サイトトップ

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

Adobe Flash CS3 Professional ActionScript 3.0

□02 スクリプトによるアニメーション

02-01 関数・メソッドを使う
60秒で1回転する秒針のアニメーションをつくってみましょう。まず、前章「ActionScript 3.0を使ってみよう」で学習した知識でも、それらしいものはできそうです。

タイムラインで1秒をカウントする
フレームレートがデフォルトの12fpsだとすると、タイムラインに12フレーム作成して、アニメーションをループさせれば、1秒ごとにインスタンスを回転することができます(図02-001)。メインタイムラインに配置した秒針のムービークリップインスタンスにはbar_mcという名前をつけ、6度ずつ回転させます(スクリプト02-001)。

図02-001■メインタイムラインは1秒12フレームでループ

メインタイムラインの第1フレームアクションはムービークリップインスタンスbar_mcを6度回転させる

スクリプト02-001■メインタイムラインの第1フレームアクションでムービークリップインスタンスを回転

bar_mc.rotation += 6;

メインタイムラインの上記第1フレームアクション(スクリプト02-001)は、メインタイムラインに配置されたムービークリップインスタンスbar_mcの角度を設定するrotationプロパティに6を加算します。メインタイムラインは1秒12フレームがループ再生されますので、1秒ごとに第1フレームアクションが実行され、6度ずつ回転し、60秒で1回転(360度回転)することになります。

しかし、この秒針は、時間の経過が正確ではありません。フレームレートが12fpsに設定されていても、1秒間に必ず12フレーム再生されるとはかぎらないからです。

Flashのアニメーションは、原則としてすべてのフレームを再生・表示します。あるフレームの表示に時間がかかったとしても、その後の再生フレームをスキップしたりしません。そのため、アニメーションの処理負荷の高さや、再生環境の仕様(スペック)によって、実際のフレームレートは変わってしまいます。また、フレームレートそのものが、ミリ秒単位の正確さを備えたものではありません。

getTimer()関数の使い方
コンピュータは、ご存じのように時計を内蔵しています。ActionScriptには、その時計の時刻を取得する関数(前掲Word 01-005)があります。その関数で調べた値を使えば、もっと時刻に正確なアニメーションがつくれます。ここでは、getTimer()関数を使うことにします。

スクリプトを記述したフレームを選択して、[アクション]パネルにgetTimer()と記述したら、この関数名をドラッグしてハイライトし、[ヘルプ]ボタンをクリックしてみましょう(図02-002)。[ヘルプ]パネルが開いて、getTimer()関数の説明が表示されます(図02-003)。

図02-002■[アクション]パネルで関数を選択して[ヘルプ]ボタンを押す

[ヘルプ]パネルが開いて、[アクション]パネルで選択した関数やプロパティの説明が表示される。

[ヘルプ]にはActionScript 3.0について、そのプログラミングに関する説明のほか、個々の関数(メソッド)やプロパティの使い方を解説したリファレンス(辞書)が備わっています。[アクション]パネルで関数を選択して表示されたのはこのリファレンスの解説です。

初心者には難しい内容も含まれており、邦訳に難のある箇所やときおり情報自体の誤りが見つかることもあります。けれども、その情報量においては、ActionScript 3.0に関するもっとも詳しいドキュメントです。やみくもにGoogle検索する前に、まずヘルプを確認することが大切です。そこで、本書ではヘルプの読み方についても、折にふれて説明を加えることにします。

図02-003■[ヘルプ]パネルに表示されたgetTimer()関数の解説

関数についてはシンタックス(文法)や戻り値、スクリプトの例などが記載されている。

[ヘルプ]の[getTimer()関数]の項の最初に示されているのが、関数のシンタックス(前掲Word 01-002)です。

public function getTimer():int

publicは「公開された」という程度の意味に捉えて、当面気にしなくて構いません(publicの意味については、クラス定義で詳しく解説します)。functionは関数であることを示します。関数は「メソッド」と呼ばれることもあります。getTimer()は関数の呼出し方を表し、括弧()内が空なのは引数(パラメータ)がないことを意味します。

括弧()に続くコロン(:)の後に示されているのは、戻り値のデータ型です。intは整数のデータ型です。Number型が小数値(プログラミングでは「浮動小数点数値」と呼ばれます)を取れるのに対して、int型には整数値しか与えることができません(後述Tips 02-014「数値のデータ型」参照)。

したがって、シンタックスの記載からは、この関数はgetTimer()と書いて呼出すと整数値が返されるとわかります。具体的にどのような値が返されるのかは、シンタックスの後に説明されています。

AS1&2 Note 02-001■int型
ActionScript 2.0には、数値のデータ型としてはNumberしかありません。

Flash Playerが「初期化」されるというのは、Flash Playerが起動して、SWFムービーが再生できる状態になることを意味します。つまり、getTimer()関数は、Flash Playerがスタートしてからの経過時間をミリ秒(1/1000秒単位)で返すということです。

ヘルプはさらに、「戻り値」やスクリプトの「例」についても詳しく紹介しています。けれど、当面はここまで理解すれば十分でしょう。

Maniac! 02-001■int型は数値を変換する
Number型の小数値をint型に設定すると、エラーは発生することなく、整数値に変換されます。つぎのスクリプト02-002を[ムービープレビュー]で試せば、[出力]パネルには小数点以下を切捨てた1が表示され、エラーは起こりません。

スクリプト02-002■Number型の小数値をint型に指定した変数に代入

var n:Number = 1.5;
var i:int = n;
trace(i);

もっとも、データ型にそぐわない値を代入することは、お勧めできません。データ型の指定を値に一致させて変えるか、値をデータ型に合わせて変換するのが、無用のトラブルを避けるスクリプティングだといえます。

getTimer()関数で時間の経過を調べる
getTimer()関数を使って、SWFムービー開始からの時間とともに回転する、秒針のアニメーションをつくってみましょう。取りあえず、タイムラインは前に作成したムービー(図02-001)の状態のまま、フレームアクションを書替えることにします(図02-004・スクリプト02-003)。

図02-004■getTimer()関数を使ったフレームアクションに書替える

getTimer()関数で調べたミリ秒を秒数に換算して、1秒当たりの回転度数を乗じたうえで、ムービークリップインスタンスの角度に設定。

getTimer()関数が返す経過時間はミリ秒ですから、1000で割れば秒の値になります。秒針は1秒に6度回転しますので、得られた経過秒数に6を乗じて、ムービークリップインスタンスの角度に設定すればよいでしょう(スクリプト02-003)。

12フレームで1秒をカウントした前のムービー(図02-001)では、フレームループのたびに6度を加算(+=)しました。しかし、getTimer()関数を使った計算では、経過秒数そのものが増えていきますので、6掛けた値を加算ではなく代入(=)していることにご注意ください。

スクリプト02-003■getTimer()関数で取得したミリ秒を秒数に直して角度に設定

// メインタイムライン
// 第1フレームアクション
var nSeconds:Number = getTimer()/1000;   // getTimer()で取得したミリ秒を秒に変換
trace(nSeconds);   // 確認用
bar_mc.rotation = nSeconds*6;   // 1秒につきムービークリップを6度回転させる

ダブルスラッシュ//は「行コメント区切り記号」と呼ばれ、この記号以降の1行はスクリプトとしては無視されます。無視される部分の記述をコメントといい、[アクション]パネルのデフォルト設定ではグレーで表示されます。行コメント区切り記号//を使う目的は、おもにふたつあります。

第1は、文字どおりコメントとして、スクリプトを書いた人が注釈やメモを加えるためです。上記スクリプト02-003のように、フレームアクションの記述場所や処理内容の説明を添えると、スクリプトがわかりやすくなります。コメントは、ステートメントの後に書き加えることもできます。その場合、ステートメントはもちろん有効で、行コメント区切り記号//の後のその行の残りがコメントと認識されます。

第2に、動作確認のため一部ステートメントの処理を一時的に外したり、修正前の記述を無効化しつつ履歴として残したりするために用いられます。ステートメントをコメント区切り記号で無効化することを、「コメントアウト」といいます。たとえば、上記スクリプト02-003でtrace()関数の[出力]結果を確認し終えたら、最終的なムービーには不要ですので、つぎのようにコメントアウトすればよいでしょう。

// trace(nSeconds);   // 確認用

Tips 02-001■[traceアクションを省略]
[出力]パネルは、ブラウザやプロジェクタには存在しません。ですから、最終コンテンツにtrace()関数の記述があっても無意味です。しかし、trace()関数のステートメントはSWFに書出されます。無駄な記述がSWFに残らいなようにするには、そのtrace()したステートメントをコメントアウトするか、[ファイル]メニューの[パブリッシュ設定]で[Flash]タブの[traceアクションを省略]にチェックをつけて[パブリッシュ]するとよいでしょう。

図02-005■[パブリッシュ設定]の[Flash]タブで[traceアクションを省略]にチェック

[パブリッシュ]したら、すぐに[traceアクションを省略]のチェックを外してから、ダイアログボックスを閉じる。

ただし、そのまま[OK]でダイアログボックスを閉じてしまうと、[ムービープレビュー]のときに[出力]されなくなります。[パブリッシュ]したら、すぐに[traceアクションを省略]のチェックを外し、それから[OK]ボタンをクリックするように心がけるようお勧めします。


Tips 02-002■プロックコメント
「プロックコメント区切り記号」(/*..*/)を使うと、複数行をまとめてコメントにすることもできます。開始区切り記号(/*)から終了区切り記号(*/)までの間は、複数行にわたって無効化されます。

/* コメント開始
この間はコメントとして無効化
この間はコメントとして無効化
この間はコメントとして無効化
コメント終了 */

上記スクリプト02-003を[ムービープレビュー]で試すと、秒に換算された経過時間が、たとえばつぎのように[出力]されます(図02-006)。ただし、具体的な数値は、環境などによって変わります。

図02-006■秒に換算された経過時間が[出力]パネルに表示

12fpsで12フレームごとの出力なので、約1秒間隔で表示される。ただし、具体的な値は環境によって異なる。

フレームアクションの実行は秒の切り替わりのタイミングとは、少しずれています。また、他に処理やアニメーションを加えれば、フレームアクションの実行間隔にも遅れが生じることは十分考えられます。ですから、フレーム数は少なくした方が、より細かいタイミングで、時間の経過と秒針のアニメーションとを同期することができるでしよう。そこで、タイムラインのフレーム数を、計2フレームに縮めてみます(図02-007)。

図02-007■タイムラインのフレーム数を縮める

フレームアクションが短い時間間隔で実行されるので、経過時間と秒針の動きがより細かく同期。

Tips 02-003■連続したフレームの長さを変える
連続したフレームの終端に[Ctrl](Windows)/[command](Macintosh)キーを押しながらマウスポインタを合わせると、カーソル形状が左右方向の矢印(←→)に変化します。そのまま左右にドラッグすると、連続したフレームの長さを縮めたり伸ばしたりすることができます(図02-007)。

[ムービープレビュー]を行うと、秒針は1秒刻みではなく、約1/6秒間隔で滑らかに回転します。これを、つぎはスクリプトで、1秒刻みの動きにしてみましょう。

Math.floor()メソッドで小数点以下を切捨てる
秒針のアニメーションをよりきめ細かく経過時間と一致させるため、約1/6秒(12fpsで2フレーム分の)間隔でフレームアクションを実行する構造はそのまま維持します。すると、秒数に換算した経過時間の小数点以下の端数を切捨てれば、1秒刻みで秒針のムービークリップインスタンスを回転させることができます。

この小数点以下を切捨てるのが、Mathクラスのfloor()メソッドです。今の段階では「クラス」というのは、さまざまな関数やプロパティを集めて、ひとつのカテゴリーとしてまとめたものと考えればよいでしょう。そして、前述(Word 01-005)のとおり、クラスに定義された関数を「メソッド」と呼びます。

つまり、Mathクラスという数学的な計算をするカテゴリーに属する、小数点以下を切捨てる関数がfloor()メソッドです。floor()メソッドはMathクラスに属しますので、ドット(.)を使って、Math.floor()と記述します。ここでまた、リファレンスの説明を確認しましょう。

図02-008■Math.floor()メソッドについての[ヘルプ]の解説

Math.floor()メソッドは、引数に指定された小数値の小数点以下の値を切捨てて、整数値を返す。

まず、シンタックスから見ていきます。publicは、当面気にしないことにしました。その後にstaticという用語があります。日本語では「静的」と訳されます。これは、メソッドへのアクセスの仕方を示しています。

public static function floor(val:Number):Number

水平座標や角度のプロパティxrotationにアクセスするとき、ターゲットとして操作対象のインスタンスを指定しました。メソッドの場合にも、ターゲットの指定は必要になります。したがって、メソッドはつぎのようなかたちで呼出します(括弧()の中の引数に角括弧[]がついているのは、ヘルプやリファレンスの記載ではオプションであることを示します。すなわち、指定される場合もあれば、されない場合もあります)。なお、ターゲットのことを「参照」と呼ぶこともあります。

メソッドの呼出し
ターゲット.メソッド([引数]);

static(静的)なメソッドというのは、このターゲットあるいは参照がクラス名そのものになることを意味します。つまり、ムービークリップのプロパティのように個々のインスタンスをターゲットに指定するのでなく、つねにクラス名を参照して呼出します。Mathクラスのfloor()であれば、いつもMath.floor()で呼出せばよいということです。

つぎに、メソッドに渡す引数(パラメータ)は、数値または式です。「」には、四則演算の計算式はもちろん、たったひとつの変数や、関数の呼出しも含まれます。処理結果の値が得られる(返される)ものは、すべて式になります。ただし、Math.floor()メソッドは引数に続くコロン(:)の後にNumberと指定されているのは、引数がNumber型つまり数値でなければならないことを示します。

そして、戻り値は、引数に指定された数値の小数点以下を切捨てた整数値です。もっとも、データ型はintでなく、Numbetで指定されています。ここで「切捨て」とは、対象となる数値以下のもっとも近い(大きい)整数値を求めることです。

Tips 02-004■負の数値の切捨て
Math.floor()関数の切捨てとは、指定された引数値以下の最大の整数を返すことです。引数がマイナスの場合の結果には、注意する必要があります。たとえば、Math.floor(-1.9)は-2を返します。戻り値は、引数に指定した-1.9以下の整数でなければならないからです。


Maniac! 02-002■int()関数の負の数の扱い
int()関数(クラスには属さないのでメソッドと呼びません)は、小数点以下の端数を単純に取除いて、その整数部分だけを取出します。したがって、正の数値については、Math.floor()メソッドと同じ結果になります。しかし、負の数値については、int()関数は絶対値を比較したとき指定の数値以下となる、もっとも近い整数を返します。たとえば、int(-1.9)は-1になります。

与えられた数値の絶対値を考えて端数を除くint()関数の処理は、直感的にわかりやすいといえます。たとえば、Microsoft Excelの切捨ての関数(rounddown())も、この方式で値を算出します。

ただし、この計算の仕方は、0を中心に正負で対称(反対)の処理を行っていることになります。プログラミング上は、数値の正負を問わず同じ処理を行うMath.floor()の仕様の方が、有利な点は多いでしょう。

リファレンスの説明から、Math.floor()メソッドは、つぎのような構文で使えることがわかります。「変数」には、「小数値」を小数点以下で切捨てた整数値が代入されます。「小数値」には、式が入っても構いません。

var 変数:Number = Math.floor(小数値);

それでは、前に作成したスクリプト02-003に、Math.floor()メソッドの処理を加えて、秒針が1秒刻みで回転するように書替えてみましょう(スクリプト02-004)。

スクリプト02-004■取得したミリ秒をMath.floor()関数により1秒刻みの値にして回転

// メインタイムライン
// 第1フレームアクション
var nSeconds:Number = getTimer()/1000;   // getTimer()で取得したミリ秒を秒に変換
// trace(nSeconds);   // 確認用
bar_mc.rotation = Math.floor(nSeconds)*6;   // Math.floor()関数で1秒刻みの値にして6度ずつ回転させる

[1]上記フレームアクション(スクリプト02-004)の3行目、コメントを除いた第1ステートメントは、以前(スクリプト02-003)のまま変更がありません。

[2]フレームアクションの4行目、もとのスクリプト(02-003)の第2ステートメントは、取得される秒数値の確認を終えましたので、コメントアウトしました。再度秒数値を確認したいときには、先頭の//(行コメント区切り記号)を削除すれば、[ムービープレビュー]時ふたたび[出力]パネルに秒数値が約1/6秒間隔で表示されます。

[3]フレームアクションの5行目、新たな第2ステートメントに、Math.floor()メソッドの処理を加えました。変数nSecondsに代入された小数値の秒数を、Math.floor()メソッドの引数に渡して、1秒単位の整数にします。整数の秒数値に6度を乗じてインスタンスの角度に設定すれば、1秒刻みで秒針のムービークリップインスタンスが回転します。

なお、変数に6を掛けたnSeconds*6を、Math.floor()メソッドの引数に指定しないよう注意しましょう。そうすると、角度の値が小数点以下で切捨てられ、1度単位で秒針が回転してしまいます。



02-02 変数を使う
前節02-01で作成したムービーに、たとえばオープニングが追加されて、秒針のアニメーションは第120フレームからのスタートになったとしましょう(図02-009)。getTimer()関数はFlash Playerが再生を開始してからの経過時間を返しますので、フレームレートが12fpsであれば秒針のアニメーションは約10秒(=120フレーム/12fps)の位置から始まることになります。

秒針のアニメーションを0秒からスタートする
秒針のアニメーションを必ず0秒の位置から開始するには、第120フレームに到達したときのgetTimer()関数の戻り値を覚えておく必要があります。その到達時の値を別の変数に取っておいて、経過秒数を計算するときにgetTimer()関数の現在値からその到達時の値を差引けば、秒針のアニメーションが0秒から始められます。

Tips 02-005■前のフレームに戻る
秒針のアニメーションを実行する2フレームを単純に第120フレームに移動しただけでは、最終(第121)フレームに達するとまた第1フレームに戻ってしまいます。前の(第120)フレームに戻るには、最終(第121)フレームにつぎのスクリプト02-005を追加する必要があります。

図02-009■秒針のアニメーションの開始を第120フレーム目に移動

秒針のアニメーションは、約10秒経過した位置からスタートすることになる。

スクリプト02-005■前のフレームをループ再生

// メインタイムライン
// 第121フレームアクション
gotoAndPlay(currentFrame-1);

gotoAndPlay()は、引数に指定したフレームに再生ヘッドを移動して、再生を続けるメソッドです。また、currentFrameプロパティは、現在再生ヘッドのあるフレーム番号を返します。ともに、ターゲットの指定がありませんので、スクリプトを記述しているメインタイムラインを対象とします(Tips 01-009「インスタンスは親のプロパティ」参照)。

したがって、メインタイムラインの第121フレーム目に再生ヘッドが達すると、このフレームアクションが実行され、現行フレーム番号121のひとつ前の第120フレーム目に戻り、ループ再生を続けることになります。

では、前のムービーのスクリプト02-004を、つぎのように書替えればよいでしょうか。変数nStartに、第120フレームに到達したときのgetTimer()関数の戻り値を代入しています。しかし、これでは秒針は0秒を指したまま、ピクリとも動かなくなります(図02-010)。

var nStart:Number = getTimer()/1000;
var nSeconds:Number = getTimer()/1000;   // 右辺は第1ステートメントと同じ
// ふたつの変数値が等しいのでムービークリップの角度は0になる
bar_mc.rotation = Math.floor(nSeconds-nStart)*6;

図02-010■0秒の位置で動かない秒針

秒針のムービークリップインスタンスの角度が、0に設定され続けている。

上記のスクリプトを第120フレームアニメーションとして記述すれば、第1ステートメントで変数nStartには第120フレームに到達したときのgetTimer()関数の値が代入されます。けれど、変数nSecondsに経過時間を代入する、第2ステートメントの右辺は第1ステートメントとまったく同じです。

もちろん、第120フレームに到達したとき、ふたつの変数nStartとnSeconds値は同じでないと、秒針は0からスタートしません。しかし、問題はこのフレームアニメーションが、ループ処理されたときです。経過秒数を新たに取得する変数nSecondsの値とともに、最初に第120フレームに到達したときの秒数を保持すべき変数nStartの値も更新されてしまうのです。ふたつの変数値がつねに等しい訳ですから、秒針は0からまったく動かないことになります。

この問題を解決するには、変数nStartの値が書替えられないようにすることです。方法は、いくつか考えられます。たとえば、変数nStartの値はひとつ前の第119フレームで設定するようにして、アニメーションの繰返し(ループ)処理は第120フレームアクションで実行する方法もありえます(この場合厳密にいえば、nStartの値は第120フレーム到達時より、1フレーム分つまり1/12秒ほど早くなります)。

しかし、本書では、スクリプトはできるだけひとつの場所にまとめて書くようにします。ひとまとまりであるはずの処理があちこちに細切れで書かれていると、全体の流れを把握しにくくなるからです。とくにこの章では、最終的に秒針のムービークリップシンボル内に、スクリプトをまとめます。すると、[ライブラリ]からムービークリップシンボルをステージにドロップしただけで、そのインスタンスはただちに秒針として動作するようになります。つまり、コンポーネントのようなパーツ化を目指そうということです。

ではまずは、再生ヘッドが秒針のアニメーションを開始するフレームに到達したときの経過秒数は、秒針のムービークリップシンボル内の第1フレームに変数nStartとして設定することにしましょう(スクリプト02-006)。秒針のムービークリップをシンボル編集モードで開いて、スクリプト用のレイヤーを追加したら、フレームアクションを記述します(図02-011)。

スクリプト02-006■秒針のムービークリップにスタート時の秒数値を変数として設定

// 秒針のムービークリップシンボル
// 第1フレームアクション
var nStart:Number = getTimer()/1000;
trace(nStart); // 確認用


図02-011■ムービークリップシンボルに第1フレームアクションを記述

ムーピークリップシンボル(Bar)内は1フレームのみ。

実質のステートメントは、変数nStartにgetTimer()関数の戻り値を秒数に直して代入する1行だけです。確認のため、第2ステートメントで、その秒数値をtrace()関数で[出力]しています。メインタイムラインのフレームアクションは、以前のスクリプト02-004のまま、まだ変更する必要はありません。

[ムービープレビュー]を実行すると、12fpsで第120フレームに秒針のムービークリップインスタンスを配置してあれば、秒針が表示された最初に1度だけ約10の数値が[出力]パネルに表示されるはずです(図02-012)。

図02-012■秒針のアニメーション開始時の経過秒数が[出力]

約10の数値が1度だけ[出力]される。

ムービークリップシンボルの中には1フレームしかありません(図02-011)ので、再生ヘッドの移動が発生しません。したがって、その第1フレームアクションは、1度しか実行されないことになります(Tips 01-010「フレームアクションと画面の描画」参照)。これで、秒針のアニメーション開始時の経過秒数を変数nStartに設定し、その値を変わらず保持することができます。

それではつぎに、メインタイムラインのフレームアクション(スクリプト02-004)に修正を加えて、秒針のムービークリップシンボルで設定した変数nStartの値を利用することにします。

ただし、メインタイムラインからムービークリップシンボル内で定義した変数nStartには、単にnStartと記述しただけではアクセスすることはできません。なお、値を調べたり設定するために変数やプロパティにアクセスすることを、変数・プロパティを「参照する」と表現することもあります。

前章01-05「変数を使う」で、変数は「メモリ」だと説明しました。もう少し詳しくいうと、フレームアクションでは、この「メモリ」は原則としてタイムラインごとに管理されます。つまり、メインタイムラインで定義された変数はメインタイムラインにメモリされ、ムービークリップ内で定義された変数はそのインスタンスにメモリされるのです。そうした変数の参照の仕方は、丁度プロパティにアクセスする場合と同じです。つまり、ターゲットパスをドット(.)で指定したうえで、変数を参照する必要があります。

たとえば、メインタイムラインにムービークリップインスタンスmy_mcを配置し、メインタイムラインからmy_mcに定義された変数myVarを参照するには、つぎのように記述します。これは、インスタンスmy_mcのxプロパティにアクセスするとき、my_mc.xと書くのと同じです。

my_mc.myVar

Tips 02-006■変数とプロパティは同じ
ActionScript上は、変数とプロパティは同じものと理解して差支えありません。変数は、スクリプトを書く人が自由に定義できます。それに対して、ActionScript定義済みのクラスに、予め定められている変数をプロパティと呼ぶと考えてもよいでしょう。この点は、後でクラス定義を学ぶと、より明らかになります。

ムービークリップインスタンスbar_mc内に定義した変数nStartを、メインタイムラインから参照する方法がわかりました。そこで、メインタイムラインのフレームアクション(スクリプト02-004)は、つぎのように書替えられます(スクリプト02-007)。

スクリプト02-007■

// メインタイムライン
// 第120フレームアクション
var nSeconds:Number = getTimer()/1000;
// ムービークリップインスタンスbar_mcに設定した変数nStartの値を参照
// 秒針のアニメーション開始時からの経過時間で回転
bar_mc.rotation = Math.floor(nSeconds-bar_mc.nStart)*6;

[ムービープレビュー]を実行すると、秒針のアニメーションは0秒の位置からスタートします。ただし、最初に一瞬、0から少しずれた角度で秒針が表示されます。この現象は、最終的にムービークリップシンボル内にスクリプトをまとめることで解消します。ですからこの問題の理由も、その際に改めてご説明することにします。

図02-013■最初に一瞬0の位置からずれた角度の秒針が表示

その後のアニメーションは、0秒を起点にして時間を刻む。

Tips 02-007■最初の秒針のずれを直す
最初に一瞬表示される秒針の角度のずれを、取りあえず現状のスクリプトをベースに修正するとすれば、ムービークリップシンボルに記述したフレームアクションにステートメントを1行加えればよいでしょう。

スクリプト02-008■秒針のムービークリップに角度のずれを補正するステートメント追加

// 秒針のムービークリップシンボル
// 第1フレームアクション
var nStart:Number = getTimer()/1000;
trace(nStart); // 確認用
rotation = 0;   // [追加]インスタンスの角度を0に設定

変数について
ここで、変数について、少しまとめておきましょう。まず、変数は識別子(Word 01-001)でなければなりません。つまり、(1)使える文字は半角の英数字か「_」(アンダースコア)(または「$」(ドル記号))で、(2)先頭は数字以外、(3)大文字と小文字を区別して、(4)「予約語」は使用できません。

Maniac! 02-003■日本語の識別子
ActionScriptはUnicodeに対応しているので、実際には全角の日本語(2バイト文字)を識別子に使っても動作します。たとえば、ムービークリップシンボルのインスタンスに「動画片」などというインスタンス名をつけて、スクリプトでコントロールすることも可能です。

ただ、あえて日本語を用いる利点はあまり考えられません。また、全角入力の状態のまま気づかずに、半角のつもりでスクリプトとしてスペースを入力するとエラーになってしまいます。テキスト(文字列)として用いる以外は、日本語の使用はお勧めしません。

ここで、「予約語」というのは、オンラインヘルプ「ActionScript 3.0 のプログラミング」の[ActionScript 言語とシンタックス] > [シンタックス]の項に、「キーワードと予約語」として一覧表が掲げられています(Tips 02-008)。

厳密には、この「予約語」に含まれなければ、ActionScriptで使われている関数・メソッド名やプロパティ名その他のキーワードを使っても、それ自体は許されます。また、自作の(カスタム)クラスを作成する場合には、あえて既存のメソッドと同じ名前のメソッドを定義するという場合もありえます。けれど、クラスの仕組みや定義済みの名前をあえて使う意味が理解できるまでは、ActionScriptですでに使われている名前は用いないことをお勧めします。

Tips 02-008■キーワードと予約語
識別子に使用できない単語には、(1)使用できない名前としてエラーを発生する「レキシカルキーワード」(表02-001)と、(2)エラーは起きないものの特別な意味があるため使用すべきでない「シンタックスキーワード」(表02-002)、および(3)将来採用される可能性があるため使うことを推奨されない「将来の予約語」(表02-003)があります。

表02-001■レキシカルキーワード

as

break

case

catch

class

const

continue

default

delete

do

else

extends

false

finally

for

function

if

implements

import

in

instanceof

interface

internal

is

native

new

null

package

private

protected

public

return

super

switch

this

throw

to

true

try

typeof

use

var

void

while

with

 

 

 


表02-002■シンタックスキーワード

each

get

set

namespace

include

dynamic

final

native

override

static

 

 


表02-003■将来の予約語

abstract

boolean

byte

cast

char

debugger

double

enum

export

float

goto

intrinsic

long

prototype

short

synchronized

throws

to

transient

type

virtual

volatile

 

 

つぎに、フレームアクションで定義した変数は、前述のとおり、原則としてその宣言を行ったタイムラインにメモリされます。このようにタイムライン上に保持される変数を、「タイムライン変数」と呼びます。タイムライン変数を参照するには、それが定義されたタイムラインまたはムービークリップインスタンスをターゲットに指定する必要があります。

変数についてお話しする3つめは、名前のつけ方です。識別子でありさえすれば、スクリプトの動作上問題は生じません。しかし、一般的に使われている名前のつけ方に従っておくと、同じ習慣をもつ人同士でプログラムが理解しやすくなります。

[1]識別子は、変数名やインスタンス名、関数・メソッド名、プロパティ名まで含めて、小文字で始め、基本的には小文字を使います。たとえば、xrotationプロパティ、trace()関数、Mathクラスのfloor()メソッド(Math.floor())などは、すべて小文字です。ただし、複数単語で構成される名前は、第2単語以降の頭を大文字にします。curentFrameプロパティやgetTimer()関数、gotoAndPlay()メソッドなどがその例です。この慣例は、多くのプログラミング言語で踏襲されています。

Tips 02-009■小文字で始めない識別子
プログラミングの慣習として、小文字で始めない識別子を使う場合がふたつあります。第1に、クラス名は先頭を大文字にします。たとえば、MathやMovieClipクラスなど、ActionScriptでも、クラス名は大文字で始まります。

第2に、定数はすべて大文字にします。「定数」というのは、予め決まっていて、変更されることのない値です。たとえば、円周率πは、ギリシャの昔から変わっていません(何桁目まで計算で求められたかは、値そのものが変わることにはなりません)。この定数は、MathクラスにPI(Math.PI)として定義されています。

[2]「接頭辞」や「接尾辞」を使うと、便利なことがあります。「接頭辞」(prefix)は先頭に、「接尾辞」(suffix)は末尾につける記号です。いずれも、その識別子にどのようなデータが格納されているのかを示す記号が付されます。たとえば、本書では数値を代入する変数には、頭に数値(Number/Numeric)を示す"n"をつけています。これが接頭辞です。

数値の1に1を足すと、もちろん2になります。けれど、ActionScriptでは、文字列にも足し算(+演算子の処理)が定義されています。文字の"1"に"1"を足せば、ふたつの文字をつなぎ合わせて"11"になります。つまり、a=b+cというステートメントがあったとしても、そのデータが数値か文字列かによって、処理内容は異なることになります。


Word 02-001■文字列
文字の集まり、つまりテキストデータのことを「文字列」と呼びます。プログラミング言語によっては、ひと文字と複数の文字の集まりである文字列とは区別されます。しかし、ActionScriptにはそのような区別はなく、文字数に関わりなくデータ型はStringです。

スクリプトで文字列は、ダブルクォーテーション(")またはシングルクォーテーション(')で括って表記します。たとえば、変数name_strに文字列"fumio"を代入するステートメントは、つぎのとおりです。

var name_str:String = "fumio";

変数名に、格納されているデータが数値か文字列か区別できる記号がついていれば、その処理内容がわかりやすくなります。もちろん変数宣言で型指定をしていれば、データ型は明らかです。しかし、変数宣言のステートメントを探さないと、データ型は確かめられません。

ただ、接頭辞をデータ型に完全に一致させる必要はないでしょう。たとえば、数値には浮動小数点数値を扱うNumberのほか、整数のint型もあります。初めにint型で指定した変数を、小数値の処理が必要になって、Number型に変更するということは考えられます。

データ型は変数宣言の型指定だけ変えればよいのに対して、変数名はそれを使ったすべてのステートメントが修正の対象になります。すべてのステートメントを書替えようとすれば、間違いや見落としも生じ得ます。変数名については数値演算をすることがわかれば十分で、それ以上の詳しいデータ内容は型指定に委ねてよいでしょう。ですから、本書では小数値も整数値も、変数の接頭辞には"n"を使います。

Maniac! 02-004■ハンガリアン記法
おもに接頭辞を使って変数の情報を表す名前のつけ方は、「ハンガリアン記法」(Hungarian notation)と呼ばれます。考案したマイクロソフトのチャールズ・シモニー氏が、ハンガリーの出身であったことに由来するといわれます。

使われた接頭辞が複雑で逆にわかりにくくなりがちだったため、最近のプログラミング言語ではあまり使われないようです。

接頭辞を使うとよいことのもうひとつは、予約語やActionScriptの定義済みの名前との重複が防げることです。たとえば、"start"とか"stop"といった単語は、識別子として使いたくなることが少なくありません。けれど、startはPrintJobやSound、Tweenなどのクラス、stopはMovieClipやTimer、Tweenなどのクラスのメソッド名と重複します。だからといって、別の単語を探すのも面倒です。このとき、数値を代入する変数なら、nStartとかnStopという名前にすると、簡単に重複が避けられます。そうすれば、変数の名前に悩まなくて済みます。

「接尾辞」は、記号を変数の末尾につけるという以外、目的などはとくに接頭辞と変わりません。本書でムービークリップインスタンス名の末尾につけた"_mc"は、接尾辞の例です。一般のプログラミング言語では、接尾辞を使うことはあまり多くないようです。しかし、ActionScriptでは、[アクション]パネルまたはスクリプトウィンドウ内で、接尾辞をつけた識別子に続けてドット(.)を打つと、「コードヒント」が表示されます(図02-014)。

図02-014■接尾辞をつけるとコードヒントが表示される

プロパティやメソッドの名前をタイプし始めると、その文字で始まる項目がハイライトする。

Word 02-002■スクリプトウィンドウ
スクリプトウィンドウは、外部ActionScriptファイルを記述するために使われます。[ファイル]メニューから[新規]を選び、[ActionScript(AS)ファイル]を選ぶと開きます。本書では、カスタムクラスを作成する際に、スクリプトウィンドウを利用します。

「コードヒント」は、そのインスタンスに対して使えるプロパティやメソッドの一覧を表示するポップアップメニューです。項目はマウスや矢印キーで選択することもできますし、ドット(.)に続けて文字をタイプし始めると、その文字で始まる項目がハイライトされます。[Enter](Windows)または[return](Macintosh)で、選択が確定します。

もっとも、接尾辞を使わなくても、変数宣言で型指定をすれば、その指定した型をもとにコードヒントは表示されます。ですから、コードヒントを利用するために接尾辞を使う必要性は、あまりありません。ただ、接尾辞によるコードヒントの表示は、Flash MXから採用されています。したがって、ActionScriptを書く人たちの間では、接尾辞は識別子の内容を示す記号として比較的広く知られています。

本書では、識別子の名前には、接尾辞が定まっているデータについてはそれを用い、接尾辞がとくにないデータについては接頭辞を使うようにします。接頭辞・接尾辞は、必ずしも使わなければならないというものではありません。わかりやすい名前のつけ方を、予め決めておくことが大切なのです。会社やプロジェクトチームでそうした規則を定めたなら、もちろんそれに従って構いません。



02-03 関数(function)を定義する
秒針のアニメーションのサンプルに戻りましょう。

秒針が最初に一瞬ずれる訳 − NaN(非数) −
ムービークリップのスクリプト02-006およびメインタイムラインのスクリプト02-007で、一応0秒の位置から秒針のアニメーションが開始したものの、最初の一瞬だけ角度のずれたインスタンスが表示されてしまいました。この理由と問題点を、明らかにしましょう。なお、動作確認に時間がかかるので、アニメーションの開始は第1フレームに戻し、もとの2フレーム分のムービーとします(図02-015)。

図02-015■メインタイムラインのフレームアクションに確認用のtrace()関数を追加

秒針のアニメーションは、第1フレームから2フレーム分のムービーに戻す。フレームアクションのコメントは、一部削除した。

そして、メインタイムラインのフレームアクション(スクリプト02-007)に、確認のためにつぎのようなtrace()関数のステートメントを追加します(図02-015。追加先は、どの行でも構いません)。trace()関数には、カンマ(,)区切りで複数の引数を渡すことができます。引数の値は[出力]パネルに、スペース区切りの1行で表示されます。

trace(bar_mc.nStart, nSeconds-bar_mc.nStart);

AS1&2 Note 02-002■trace()関数の引数
ActionScript 1.0/2.0では、trace()関数にはひとつの引数しか指定できません。

[ムービープレビュー]を行うと、[出力]パネルにはつぎのような値が表示されます(具体的な数値は、環境によって変わります)。ムービークリップのフレームアクションは、変更していません(スクリプト02-006)。したがって、最初に1度だけ変数nStartの値が、[出力]パネルに表示されます。値がスペース区切りで1行にふたつ[出力]されるのは、メインタイムラインで繰返し実行されるフレームアクションのtrace()関数の結果です。

図02-016■trace()関数から[出力]パネルに表示された値

1行に値がふたつ表示されているのは、メインタイムラインのtarce()関数の[出力]。値がひとつなのは、ムービークリップのフレームアクションに記述したtrace()結果。

すると、[出力]パネルの第1行目にふたつNaNと表示されているのは、メインタイムラインのtrace()関数の結果だということになります。NaNは「非数」(Not a Number)と呼ばれる、特別な値です。Numberで型指定された変数に、まだ値が設定されていないときに返されます。そして、第2行目に数値がひとつ表示されているのが、ムービークリップからの[出力]です。

Word 02-003■NaN
NaNは、数値演算やデータ型の変換を行ったときに、数値として表せない結果になったとき返される特別な値です。IEEE-754規格に基づきます。

NaNという変数名は非数(Not a Number)からきています。たとえば、0を0で除算すると、NaNになります。0による除算は、未定義なためです(0でない値を0で除算することも、数学の演算上は未定義です。しかし、ActionScriptでは除される値が正の場合はInfinity、負の場合には-Infinityを返します)。

NaNを数値演算の対象とすると、結果は必ずNaNになります。ただし、データ型は数値(Number)です。したがって、「非数」というよりは、「数値演算不能値」と理解する方がよさそうです。なお、Number型で指定した変数のデフォルト値はNaNです。

[ヘルプ]のNaNについての解説は、悲しいほどあっさりしています(図02-017)。詳しい内容を知りたい場合には、筆者サイトの「NaN」(<http://www.fumiononaka.com/TechNotes/Flash/FN0108015.html>)をお読みください。

図02-017■NaNについての[ヘルプ]の解説

解説は、これだけしか記載されていない。

スクリプトには、実行の順序があります。フレームアクションは、親のタイムラインから先に処理されます。つまり、最初にメインタイムラインのフレームアクション(図02-015)が実行され、その後ムービークリップのフレームアクション(スクリプト02-006)が処理されるのです。ということは、ムーピークリップ内で変数nStartの値が代入される前に、1度メインタイムラインのスクリプトが実行されてしまうことを意味します。

Numberで型指定された変数に、値を設定する前にアクセスすれば、NaNと認識されます。そして、NaNに対しては、どのような数値演算を行おうが、結果はNaNになってしまいます。したがって、最初のメインタイムラインのフレームアクション(図02-015)は、インスタンスbar_mcのrotationプロパティにNaNを設定してしまいます。その結果、角度のずれた秒針のインスタンスが表示された訳です。

AS1&2 Note 02-003■プロパティにNaNを設定したとき
ActionScript 2.0/1.0では、プロパティにNaNを代入しても、値は設定されませんでした。たとえば、ムービークリップインスタンスの_rotationプロパティにNaNを代入しても、プロパティ値は変更されずにそれ以前の値が保持されます。ですから、インスタンスの表示には変化が生じません。

メインタイムラインのフレームアクション(図02-015)がループ処理される2回目以降には、ムービークリップインスタンスbar_mcに変数nStartがすでに設定されていますので、意図したとおりに秒針が回転することになります。[出力]パネルの表示(図02-016)の第3行目以降で、ムービークリップインスタンスbar_mcの変数bar_mc.nStartと経過秒数nSeconds-bar_mc.nStartのふたつの値が正しく数値として示されているのは、そのことを意味します。

対処法は、いくつか考えられます。たとえば、メインタイムラインの第1フレームにはフレームアクションを置かず、まずムービークリップインスタンスを先に登場させてしまってもよいでしょう。すると、ムービークリップ内のフレームアクションが実行されて、変数nStartは設定されます。そして、メインタイムラインのフレームアクションを第2フレームに記述して、第3フレームから第2フレームにループ処理するやり方です。

実はこれは、Flash 4の頃の手法です。もちろん、処理としてとくに問題は生じません。ただ、修正や応用がしにくく、使い回ししやすい構成ではありません。また、スクリプトをできるだけひとつの場所にまとめようという、本書のスタイルにはそぐわないといえます。

つぎに考えられるのは、メインタイムラインのフレームアクションでムービークリップインスタンスbar_mcのrotationプロパティに値を設定する前に、変数bar_mc.nStartの値がNaNでないことを確認することです。NaNであったら、値をプロパティに設定しなければよいでしょう。これは、あとでご紹介する条件判定のifステートメントを使えば可能です。このような処理が必要であったり、適切と考えられるケースもあります。

しかし、本章では関数(function)を定義することによって、処理をまとめていこうと思います。関数は、スクリプトで作成することができます。そのようなスクリプトで定義した関数は、ActionScriptのビルトイン(定義済み)関数に対して、「ユーザー定義関数」と呼ばれることもあります。

関数の定義と呼出し
関数は、変数と同じくメモりと捉えられます。フレームアクションの場合、メモリ場所がスクリプトを記述したタイムラインであることも、変数と同じです。ただ、変数が単純な値を保持するのに対して、関数は処理内容(手順)そのものをメモリします。つまり、関数を使えば、好きなときに好きなだけメモリしておいた処理を実行(呼出す)ことができるのです。

関数を使うためには、ふたつのステップが必要です。(1)第1は、関数の「定義」です。これは、関数というかたちで、処理内容をタイムラインにメモリする操作に当たります。(1)第2は、関数の「呼出し」です。一旦メモリした関数は、いつでも何度でも呼出して実行することができます。

このふたつのステップは、ネットで音楽を入手して聴く場合と似ています(表02-004)。関数の定義は、音楽データをネットからダウンロードする手順に相当します。音楽データはディスクに記録するのに対して、関数はRAMにメモリするという違いがあるだけです。このステップは、再生(音楽)あるいは呼出し(関数)の前にやっておかなければなりません。ただし、内容に変更がないかぎり、1度だけ行えば足ります。

いったんダウンロード(音楽)や定義(関数)が済めば、あとはいつでも好きなときに、何度でもディスクあるいはメモリから再生または呼出しすることが可能になります。

表02-004■関数とネットの音楽を比較
ステップ 関数(function) ネットの音楽 実行時期
1 定義 ダウンロード 最初に1回
2 呼出し 再生 ステップ1の後なら、いつでも何度でも

今回はメインタイムラインのフレームアクションに記述した処理(図02-015)を、そっくりそのままfunctionとして、秒針のムービークリップ内のフレームアクションに定義します。秒針のムービークリップシンボル内には、1フレームしかありませんので、第1フレームアクションも1度しか実行されません。しかし、関数の定義ですから、1度実行するだけで足ります。

そのうえで、関数の呼出しを、メインタイムラインの第1フレームアクションから行います。すると、メインタイムラインのループ再生により、第1フレームアクションは繰返し実行されることになります。したがって、関数も繰返し呼出されて、秒針のアニメーションが実行されるという仕組みです。

関数(function)は、つぎのように定義します。関数の記述は、その定義のキーワードfunctionで始めます。関数名は、もちろん識別子です。この関数を呼出すと、関数の本体と呼ばれる中括弧{}内に記述したステートメントが実行されます。ステートメント数は、1行にかぎらず任意です。

function 関数名([引数0:データ型, 引数1:データ型, ..., 引数n:データ型]):戻り値のデータ型 {
   ステートメント;
}

ユーザー定義関数にも、ActionScriptのビルトイン関数・メソッドと同じように、引数や戻り値を指定することができます。関数名の後の丸括弧()中に指定されている引数の指定は、角括弧[]内に記載されています。前述のとおり、角括弧[]はヘルプやリファレンスではオプションであることを示します。

Tips 02-010■関数(function)の引数は変数
関数(function)における引数の指定は、変数の宣言と同じです。ですから、名前は識別子で定義します。また、データ型も指定できます。ただし、varキーワードは使いません。また、functionの処理を行っている間だけメモリが保持され、処理が済むとクリアされる特別な変数です。

今回は、引数も戻り値も使いません。したがって、()の中は空欄になります。()の後のコロン(:)に続けて、戻り値のデータ型を指定します。戻り値がない場合には、データ型としてvoidを指定します。なお、引数や戻り値を指定する、もう少し高度な関数の定義は、後の章でご紹介します。

新たに秒針のムービークリップシンボル内に定義する関数名は、xRotateとしましょう。すると、ムービークリップのフレームアクションに定義する関数xRotateは、つぎのような構文になります。

function xRotate():void {
   // ステートメントをここに記述
}

Tips 02-011■functionの[esc]ショートカット
[esc]キーを押した後、アルファベットキーの[f]と[n]を順にタイプすると、つぎのように一気に入力されます(3つのキーは順に押せばよく、同時に押さえる必要はありません)。

function (){
}

挿入ポイントは、functionのすぐ後に置かれますので、そのまま関数名をタイプすることができます。


Tips 02-012■ユーザー定義関数名の接頭辞
本書では、ユーザー定義関数名には、ユーザーが拡張した(eXtended)機能という意味で、接頭辞として"x"を用いることにします。

ただ、一般のプログラミングでは、ユーザー定義関数の名前に接頭辞を使う例は多くありません。それでも本書では、初心者を念頭に、ふたつの理由から接頭辞を付します。

第1に、初心者には呼出しているのが、ActionScriptのビルトイン(定義済み)関数なのか、それともユーザー定義関数なのかがわかりにくいからです。接頭辞によりユーザー定義関数であることが明らかになれば、スクリプトが理解しやすくなります。

第2に、変数の場合(02-02)と同じく、ActionScriptのビルトインのプロパティやメソッド・関数などで使われている単語と重複しないためです。接頭辞をつければ、名前を考えるのが楽になります。

もっとも、学習が進んでとくにクラスを定義するようになれば、そのような配慮の意味は薄れます。その段階になったら、関数(メソッド)には、接頭辞はとくに付しません。

それでは、メインタイムラインの処理(図02-015)を、functionとしてムービークリップシンボル内の第1フレームアクションに移行しましょう(スクリプト02-009)。関数名は、xRotateです。中括弧{}内の関数本体には、メインタイムラインの第1フレームアクションに記述されたステートメントを、カット&ペーストすればよいです。

スクリプト02-009■ムービークリップシンボルのフレームアクションに関数を定義

// 秒針のムービークリップシンボル
// 第1フレームアクション
var nStart:Number = getTimer()/1000;
trace(nStart); // 確認用
// メインタイムラインの第1フレームアクションをfunctionとして移行
function xRotate():void {
  var nSeconds:Number = getTimer()/1000;
  trace(nSeconds, nSeconds-nStart);   // 確認用
  // bar_mc.rotation = Math.floor(nSeconds-bar_mc.nStart)*6;
  rotation = Math.floor(nSeconds-nStart)*6;   // ターゲットを2箇所修正
}

ただし、ムービークリップインスタンスbar_mcのrotationプロパティを設定するステートメントは、記述場所がムービークリップシンボル内に変わりましたので、ターゲットを2箇所変更する必要があります。rotationプロパティと変数nStartはターゲットがこのインスタンス自身になりますので、bar_mcの参照を削除します。メインタイムラインのステートメントも、比較のためにコメントアウトして残しました。

関数の定義は、これでできました。あとは、メインタイムラインのフレームアクションから、この関数を呼出せばよいです。関数の呼出しは、つぎのような構文で行います。

ターゲット.関数名([引数0, 引数1, ..., 引数n])

ターゲットは、変数と同じく、関数の記述されているインスタンスを参照します。引数は、角括弧[]内に記載されていますから、オプションです。関数定義に引数が指定されていれば、それに対応して、呼出しするときに引数を渡す必要があります。

今回は、ターゲットがムービークリップインスタンスbar_mcで、関数名はxRotate、そして引数は定義されていませんので、つぎのように呼出すことになります。

bar_mc.xRotate();

このステートメントを、メインタイムラインの第2フレームアクションとして記述します(キーフレームを追加します)。第1フレームでは、ムービークリップの第1フレームアクションまだ処理されないうちに、最初の呼出しが行われてしまうからです。スクリプト用レイヤーの第1フレームは、空白キーフレームにしておきます(図02-018)。

図02-018■メインタイムラインの第2フレームアクションから関数を呼出す

スクリプト用レイヤーの第1フレームは、空白キーフレームにする。

[ムービープレビュー]を行うと、秒針のアニメーションが正しく実行されます。



02-04 setInterval()関数を使う
いよいよ秒針のアニメーションを、ムービークリップシンボル内に記述したスクリプトだけで動作するようにします。メインタイムラインのスクリプトは不要になり、フレーム数も1フレームだけで足ります。[ライブラリ]から秒針のムービークリップシンボルを、ステージにドラッグ&ドロップするだけで、秒針のアニメーションが実行されるようになります。

setInterval()関数で繰返し処理を行う
指定したfunctionを、一定の間隔で繰返し呼出してくれるsetInterval()という関数があります。本節では、このsetInterval()関数を使って、秒針のアニメーションを実行してみましょう。setInterval()関数の構文は、つぎのとおりです。

var ID番号を格納する変数:uint = setInterval(呼出す関数, 実行間隔のミリ秒);

setInterval()関数には、ふたつの引数があります。第1引数が関数で、第2引数はミリ秒を示す数値です。第1引数として渡した関数が、第2引数に指定した一定のミリ秒間隔で繰返し呼出されます。

setInterval()関数は、戻り値として整数を返します。この整数は、setInterval()関数の設定に対する、いわば整理番号です。この整数は、setInterval()関数の設定をクリアして、繰返し処理を中止するときに必要になります。今回は、とくに設定をクリアする処理は、予定していません。けれど、後で必要になったときのために、変数に取得しておく癖をつけた方がよいでしょう。

Tips 02-013■setInterval()関数の設定をクリアする
setInterval()関数で設定した繰返し処理をクリアするには、clearInterval()を使います。引数には、setInterval()関数の戻り値として取得したID番号の整数を指定します。

clearInterval(クリアするsetInterval()関数のID番号)

このID番号は、setInterval()関数を呼出したときしか取得できません。ですから、ID番号の値は、忘れずに変数に設定しておきましょう。

setInterval()関数の戻り値を代入する変数には、uintという型指定がしてあります。これは、intのタイプミスではありません。int型はプラス・マイナスを含めた整数であるのに対し、uint型はマイナスを含まない整数になります。

Tips 02-014■数値のデータ型
ActionScript 3.0には、数値のデータ型は3つあります。デフォルト値は、変数に値が設定されていない場合に返される値です。

表02-005■ActionScript 3.0の数値のデータ型
データ型 デフォルト値 説明
int 0 -2,147,483,648(-231)以上2,147,483,647(231-1)以下の整数値。32ビット符号付き整数と呼ばれます。
uint 0 0以上4,294,967,295(232- 1)以下の整数値。32ビット符号なし整数と呼ばれます。
Number NaN 整数および小数値。64ビット浮動小数点数と呼ばれます。

Number型は、小数値が扱えるだけでなく、intやuint型よりも広い範囲の整数を表すことができます。ただし、小数値を使わない場合には、intまたはuintの整数データ型を指定する方が、処理は速くなります。データ型について詳しくは、ヘルプの[Programming ActionScript 3.0] > [ActionScript language and syntax] > [Data types] > [Data type descriptions]をご参照ください。

では、秒針のムービークリップシンボルの第1フレームアクションに、setInterval()関数を使ったステートメントを追加します。呼出す関数はxRotateです。同じムービークリップ内に定義されている関数ですから、ターゲットは省略して構いません。実行間隔は、100ミリ秒としましょう(スクリプト02-010)。

スクリプト02-010■ムービークリップシンボルのフレームアクションにsetInterval()関数を追加

// 秒針のムービークリップシンボル
// 第1フレームアクション
var nStart:Number = getTimer()/1000;
trace(nStart); // 確認用
var nID:uint = setInterval(xRotate, 100); // 追加
function xRotate():void {
  var nSeconds:Number = getTimer()/1000;
  trace(nSeconds, nSeconds-nStart); // 確認用
  rotation = Math.floor(nSeconds-nStart)*6;
}

setInterval()関数を使うときに重要なのは、第1引数の関数に丸括弧()はつけないということです。丸括弧()をつけると、functionを呼出してしまい、その戻り値をsetInterval()の第1引数として渡すことになってしまいます。functionを呼出すのではなく、functionとしてタイムラインにメモリされている処理内容を、setInterval()関数に渡すのです。

前に関数の定義と呼出しを、音楽のダウンロードと再生にたとえました。setInterval()関数の引数にfunctionを指定するのは、携帯に着信メロディデータを設定するのと似ています。着信メロディの設定は、携帯の着信音として楽曲のデータをメモリさせるのであって、メロディを再生する訳ではありません。再生は、着信したときに、携帯が自動的に行います。

setInterval()関数の第1引数にfunctionを指定するとき、丸括弧()をつければ呼出しになってしまいます。丸括弧()をつけないと、そのfunction名でタイムラインにメモリされている処理内容そのものを参照することになります。そして、setInterval()関数は、第2引数で指定された実行間隔のミリ秒数が経過するたびに、第1引数として渡された参照によりfunctionの処理を呼出すということなのです。

Tips 02-015■関数定義はスクリプトの実行前に処理される
関数(function)の定義は、呼出しの前に行っておく必要がありました(表02-004)。けれど、同じ[アクション]パネルのスクリプトペイン(スクリプトを記述するウィンドウ)内に、つぎのようなフレームアクションを書いても、関数は呼出されます。

xTest();
functioln xTest() {
   trace("called");
}

これはスクリプトを実行する際、第1ステートメントを処理するより前に、その中のfunction定義が先に読込まれるからです。上記スクリプト02-010でも同じ理由により、関数xRotate()を定義するより前に、setInterval関数の引数としてxRotateを参照することが可能なのです。


Maniac! 02-005■後のフレームに定義した関数
ActionScript 3.0では、タイムラインが同じであれば、後のフレームに定義した関数(function)を、前のフレームから参照・呼出しすることが可能です。

2.0/1.0では、後のフレームに定義した関数は別のスクリプトと認識されるため、タイムラインが同じでも、前のフレームアクションから参照することはできませんでした。もっとも、スクリプトはできるだけまとめて書くという観点からは、ActionScript 3.0でも2.0/1.0と同じく、第1フレームアクションとして記述するのがよいでしょう。

さて、これでメインタイムラインから、関数を呼出す必要がなくなりました。フレームアクションは不要になり、繰返し処理がなくなりましたので1フレームだけで足ります(図02-019)。

図02-019■1フレームのみでスクリプトもなくなったメインタイムライン

メインタイムラインは1フレームで足り、関数を呼出すフレームアクションも不要になった。

[ムービープレビュー]を行うと、秒針が正しく時を刻みます。また、このムービークリップシンボルをステージにドラッグ&ドロップするだけで、インスタンスは秒針のアニメーションを行います。いわば秒針のパーツができ上がったのです。

setInterval()関数の注意点
setInterval()関数を使う際の注意点を、ここでまとめておきます。第1に、すでに述べたとおり、setInterval()関数の戻り値のID番号は、必ず変数に取得しておくことを心がけましょう。このID番号を保持していないと、繰返し処理を中止したくなったときにクリアできません。

第2に、setInterval()関数の第2引数に指定した実行間隔のミリ秒数は、厳密な間隔で処理される訳ではありません。Flashは、アニメーションをひとつの重要な処理として扱っています。setInterval()関数の処理は、そのアニメーションの合間に行われます。ですから、実行間隔を100ミリ秒と指定したとしても、厳密に100ミリ秒間隔で処理が行われる訳ではありません。また、アニメーションの処理が多ければ、実行間隔に大幅な遅れが生じることもありえます。

Maniac! 02-006■setInterval()関数の実行間隔とフレームレート
setInterval()関数の第2引数の実行間隔に1ミリ秒と指定しても、デフォルトのフレームレート12fpsではせいぜい10ミリ秒弱の間隔でしか処理は行われません。おおよそ1フレーム当たりの時間の1/10が、setInterval()関数に指定できる最短の実行間隔のようです。12fpsは、1フレーム当たり約83ミリ秒(=1000/12)ですから、その1/10の約8ミリ秒が実行間隔の下限になると考えられます。

したがって、フレームレートを上げた方が、setInterval()関数に指定可能なミリ秒数は小さくできるということです。もちろん、フレームレートを上げ、setInterval()関数で実行する処理の間隔を短く指定すれば、全体としてFlash Playerの負荷が上がります。その結果、意図したパフォーマンスが得られるかどうかは、再生環境も含めて、具体的に確認しなければなりません。

第3に、アニメーションは、あくまでフレームレートで表示されるということです。スクリーンの描画は、フレームレートで更新されます。setInterval()関数の第2引数の実行間隔を100ミリ秒に指定しても、フレームレートが1fpsであれば、画面は1秒間隔でしか書替わりません。したがって、アニメーションは、1秒刻みでぎこちなく動くことになります。

図02-020■アニメーションはフレームレートで表示される

setInterval()関数の処理が100ミリ秒間隔でも、フレームレートが1fpsなら、スクリーンの更新は1秒間隔で行われる。

そうすると、スクリプトでアニメーションを行おうとする場合には、フレームレートに合わせて描画更新時に処理する方が無駄は少なそうです。次節02-05では、その手法をご紹介します。setInterval()関数は、スクリーンの描画と関係なく一定の時間間隔で行いたい処理や、フレームレートよりも頻繁にチェック・更新したい処理などに用いるのがよいでしょう。



02-05 イベントリスナーを使う
マウスクリックやキーボード入力などのユーザーのインタラクティブな操作であるとか、フレームレートによるスクリーンの描画更新やデータのロードなどのシステムで発生する処理といった、スクリプティングにとって意味のある「できごと」を「イベント」(event)と呼びます。ActionScriptでは、そうしたイベントが発生したときに行う処理を定義できます。

Word 02-004■イベント
インスタンスに対して予め定められた事象が起こったときに、発せられる信号を「イベント」といいます。「信号」は、「メッセージ」と表現されることもあります。発生するイベントの種類やタイミングは、インスタンスによって異なります。

「イベントハンドラ」というイベントに対応した処理を定義すると、イベントが発生したときに、その定義した処理が行われます。ActionScript 3.0では、「イベントリスナー」という仕組みにより、処理したい内容を関数として定義します。

イベントリスナーとは
イベントを使ったスクリプティングでは、まずどのイベントを捉えて処理を行うべきか考えなければなりません。今回はアニメーションの処理ですので、スクリーンの描画が更新されるときに発生するイベントを調べます。そしてそれは、enterFrameというイベントになります。つぎに、そのイベントを発するインスタンスは何かを探します。enterFrameイベントは、ムービークリップインスタンスから受取ることができます。

そうすると、そのイベントの受取り方を、知っておかなければなりません。ActionScript 3.0では、「イベントリスナー」という仕組みにより、イベントを処理します。

AS1&2 Note 02-004■イベントハンドラとイベントリスナー
ActionScript 2.0/1.0では、ムービークリップインスタンスやボタンインスタンスにon()あるいはonClipEvent()といったイベントハンドラアクションを記述したり、おもにフレームアクションにメソッド(コールバック関数)のかたちで定義するイベントハンドラメソッドを用いたり、イベントリスナーの機能もあったりと、処理内容によってイベントの扱いが異なりました。ActionScript 3.0では、イベントはイベントリスナーで扱うよう統一されました。

イベントリスナーを使う手順は、ホテルでモーニングコールをお願いするときと似ています。モーニングコールは、まずフロントを内線で呼出し、時刻を指定して、電話をかけてくれるよう依頼します。イベントリスナーを設定するには、インスタンスに対してaddEventListener()メソッドを呼出し、イベントを指定して、実行すべき処理を関数(function)で定義します。この実行する関数を、イベントリスナーもしくはリスナー関数と呼びます。

Maniac! 02-007■イベントリスナーとリスナー関数
他のプログラミング言語では、イベントリスナーとリスナー関数とは区別されていることがあります。その場合、イベントリスナーはイベントの発生を知らせる対象となるインスタンスで、リスナー関数はイベントが発生した際に呼出される関数・(リスナーインスタンスの)メソッドになります。

ActionScript 3.0では、リスナーのインスタンスは別途作成せず、実行すべきリスナー関数をそのままリスナーとしています。なお、ヘルプの[Programming ActionScript 3.0] > [Handling events] > [Event listeners]を併せてご参照ください。

モーニングコールの場合、フロントは指定された時刻になったら、依頼人の部屋に電話をかけます。イベントリスナーでは、インスタンスは指定されたイベントが発生したとき、定義された関数を呼出します。addEventListener()メソッドの構文は、つぎのとおりです。

ターゲットインスタンス.addEventListener(イベント, 呼出す関数)

たとえば、タイムラインに配置したムービークリップインスタンスmy_mcにイベントリスナーを設定して、enterFrameイベントで関数xAnimate()を呼出すには、以下のようなスクリプトを書きます。

my_mc.addEventListener("enterFrame", xAnimate);
function xAnimate(eventObject:Event):void {
   // 処理内容を記述
}

第1に、addEventListener()メソッドの第1引数となるイベント名は文字列(Word 02-001)で指定します。つまり、ダブルクォーテーション(")でイベント名enterFrameを括る必要があります。第2に、addEventListener()の第2引数に指定するfunctionは、setInterval()関数と同じく参照ですので、呼出しの丸括弧()はつけません。関数の定義の仕方も、setInterval()と同様です。

ただし、さらに第3として、定義した関数に引数(eventObject)があることにご注目ください。前述(02-03)のとおり、「関数定義に引数が指定されていれば、それに対応して、呼出しするときに引数を渡す必要があります」。逆に、関数呼出しのとき引数が渡される場合には、それに対応して、関数定義にも引数を指定しておかなければなりません。

イベントリスナーに指定した関数は、イベント発生時に、インスタンスから呼出されます。そのときインスタンスは、イベントについての情報が格納されたイベントオブジェクトと呼ばれる引数をひとつ渡します。したがって、リスナー関数にも、引数をひとつ指定しておく必要があるのです。渡された引数を受取らないと、ActionScript 3.0ではエラーを生じます。

AS1&2 Note 02-005■関数に対する引数の受渡し
ActionScript 2.0/1.0では、関数(function)を呼出すときに引数が渡された場合でも、funciton定義で受取る引数をとくに指定しなくてもエラーにはなりません。

引数のデータ型は、指定するイベントによって異なります。enterFrameイベントの場合は、Event型で指定することになります。イベントオブジェクトには、今回はとくにアクセスする必要はありません。ですから、イベントオブジェクトについては、後で改めて解説します。

イベントリスナーを使ったスクリプティング
それでは、秒針のアニメーション(スクリプト02-010)を、イベントリスナーを使った処理に書替えてみましょう(スクリプト02-011)。アニメーションで使うイベントは、前述のとおりenterFrameです。イベント発生時に呼出す関数(function)は、引数を指定するほかは、とくに変更する必要はありません(なお、確認用のtrace()関数のステートメントは除きました)。

スクリプト02-011■ムービークリップシンボルのフレームアクションでイベントリスナーを利用

// 秒針のムービークリップシンボル
// 第1フレームアクション
var nStart:Number = getTimer()/1000;
addEventListener("enterFrame", xRotate);
function xRotate(eventObject:Event):void {
  var nSeconds:Number = getTimer()/1000;
  rotation = Math.floor(nSeconds-nStart)*6;
}


図02-021■イベントリスナーを用いたフレーアクション

スクリーンの描画が更新されるenterFrameイベントで、リスナー関数が呼出される。

Tips 02-016■enterFrameイベント
enterFrameイベントは、ヘルプには「再生ヘッドが新しいフレームに入るときに送出され」ると説明されています。そして、「再生ヘッドが移動しない場合、またはフレームが1つしか存在しない場合、このイベントはフレームレートに合わせて継続的に送出され」ると場合分けしています([ActionScript 3.0 Language and Components Reference] > [Class DisplayObject] > [enterFrame event])。

しかし、このようにそれぞれの場合ごとにイベントの内容が異なると考えるより、そもそも再生ヘッドの移動は関係なく、スクリーンの描画の更新時に発生するイベントだと捉える方が正確でしょう。「再生ヘッドが新しいフレームに入る」(enter frame)というのは、単にこのイベント名の語源と理解すれば足ります。

スクリプト02-011の(コメントを除く)第2ステートメントで、addEventListener()メソッドを呼出しています。ターゲットを省略していますので、スクリプトを記述しているムービークリップがイベントリスナーの登録先インスタンスとして参照されます。第1引数のイベントは"enterFrame"、第2引数として呼出すリスナー関数(function)はxRotateです。

関数xRotate()の(trace()関数のステートメントを除いた)実質的な処理内容は、以前のスクリプト02-010と変わりません。ただし、前述のとおり、リスナー関数にはイベントオブジェクトが引数として渡されるため、function定義に引数をひとつ宣言しておかなければなりません。引数は、識別子であれば、名前は任意です(前述Tips 02-010「関数(function)の引数は変数」参照)。データ型は前述のとおり、Event型を指定します。

[ムービープレビュー]で確認すると、秒針のムービークリップインスタンスが、1秒ごとに時を刻みます。アニメーションの表現上は、setInterval()関数を使った場合と異なるところはありません。しかし、スクリーンの描画更新と同期してアニメーションが実行されていますので、処理に無駄がないといえます。



02-06 タイムライン変数とローカル変数
秒針のアニメーションのムービー(図02-021)に、練習のためにイベントリスナーをもうひとつ加えてみましょう。秒針のアニメーションを最初は止めておいて、秒針のムービークリップインスタンスをクリックしたときに動き出すようにします。

クリック時のイベントリスナーを加える
マウスでムービークリップインスタンスをクリックすると、そのインスタンスからclickイベントが発生します。したがって、ムービークリップインスタンスbar_mcに対して、addEventListener()メソッドを呼出し、このclickイベントに対してリスナー関数を登録します。リスナー関数名は、xStartClockとしましょう。clickイベントのリスナー関数に渡されるイベントオブジェクトは、MoueEventでデータ型を指定します。

addEventListener("click", xStartClock);
function xStartClock(eventObject:MouseEvent):void {
   // 処理を記述
}

問題は、リスナー関数xStartClock()として、どのような処理を行うかです。まず、もとのスクリプト(図02-021)の(コメントを除いた)第2行目に記述された、enterFrameイベントが指定されたaddEventListener()メソッドの呼出しを、リスナー関数の本体({}内)に移行してみましょう(図02-022)。

図02-022■秒針のインスタンスをクリックするとアニメーションが開始する

clickイベントのリスナー関数内で、enterFrameイベントについてのaddEventListener()メソッドを呼出す。

[ムービープレビュー]を確認すると、とくにエラーが出ることもなく、針のムービークリップインスタンスをクリックすれば、アニメーションが開始します。ただし、経過時間により、秒針のスタートする角度がまちまちになります。

これは、(コメントを除いた)第1ステートメントで、開始時刻を変数に取得しているからです(図02-022)。つまり、ムービークリップインスタンスへのクリックをする前に開始時刻が設定されているので、たとえば30秒待ってクリックすれば、秒針のインスタンスは30秒経過した位置から回転を始めることになるのです。

タイムライン変数とローカル変数の違い
それでは、第1ステートメントも、リスナー関数内に移行すればよいでしょうか。[ムービープレビュー]を試してみると、[コンパイルエラー]が表示されます(図02-023)。[コンパイルエラー]パネルには、エラーの原因となったステートメントやその内容が表示されます。

図02-023■[ムービープレビュー]で表示された[コンパイルエラー]

nStartが未定義であると告げる。

まず、エラーの原因は、xRotate()関数内でrotationプロパティを設定するつぎのステートメントのようです。

rotation = Math.floor(nSeconds-nStart)*6;

つぎに、そのステートメントにおけるnStartが未定義だとされています。なお、「プロパティ」と表現されているのは、「変数」と同じ意味です。ActionScriptでは、「プロパティ」と「変数」は、質的に同じものとして扱われます(前述Tips 02-006「変数とプロパティは同じ」参照)。

問題は、なぜ変数nStartが未定義と認識されたのかです。以下のステートメントで、変数nStartはvar宣言されています。そして、このステートメントを関数xStartClock()内に移行する前には、このようなエラーは発生しませんでした。実は、関数本体でvar宣言した変数は、「ローカル変数」という特別な扱いがなされます。

var nStart:Number = getTimer()/1000;

前(02-02「変数を使う」)に、変数は「原則として」タイムラインにメモリされると説明しました。その「例外」のひとつが、関数本体でvar宣言された「ローカル変数」なのです。ローカル変数は、タイムラインに保持されません。関数が実行されている間だけ確保される特別な場所にメモリされます。そして、関数の処理が終われば、原則としてその特別な場所は、メモリからクリアされます。

「ローカル」(local)というのは、ここでは「地方」ではありません。特定のかぎられた場所、「局所」という意味です。つまり、var宣言を行った関数本体というかぎられた場所の中でのみアクセスできるのがローカル変数なのです。したがって、関数の外や他の関数からは、ローカル変数にアクセスすることはできません。これが[コンパイルエラー]の示した内容です。

Word 02-005■ローカル変数
関数本体({}内)でvar宣言された変数を「ローカル変数」(local variable)といいます。関数本体の中でのみ有効で、関数の外や他の関数からアクセスすることはできません(ヘルプ[Programming ActionScript 3.0] > [ActionScript language and syntax] > [Variables]参照)。

関数が実行されたときに作成される特別なオブジェクトに、ローカル変数は保持されます。そして、関数が終了すれば、原則としてその特別なオブジェクトはメモリからクリアされます。したがって、ローカル変数は、関数が終了した後、メモリには残りません。


Maniac! 02-008■Activationオブジェクト
関数が呼出されると、Activationオブジェクトという特別なオブジェクトが作成され、そこに関数の引数(パラメータ)やローカル変数が格納されます(ヘルプ[Programming ActionScript 3.0] > [ActionScript language and syntax] > [Functions] > [Function scope])。Activationオブジェクトは、関数が呼出されたときに自動的に作成されるテンポラリなオブジェクトです(ヘルプ[ActionScript 3.0 Language and Components Reference] > [Statements, Keywords & Directives] > [with]参照)。原則として関数の実行が終われば、メモリからクリアされます。

Activationオブジェクトは、純粋な内部メカニズムですので、このオブジェクトに直接アクセスすることはできません。なお、Activationオブジェクトは、ECMA-262第3版(Word 01-007「ECMA-262第3版とECMAScript 4」参照)の「10.1.6 Activation Object」に規定されています。

では、エラーをなくし、秒針をクリックしたら初期(12時の)位置から時を刻むようにするには、どうしたらよいでしょう。第1に、nStartはローカル変数として宣言すべきではありません。他の関数(xRotate())からアクセスできるようにするためには、通常のタイムラインに保持される変数として宣言する必要があります。本書では、このタイムラインに保持される変数を、「タイムライン変数」と呼ぶことにします。

第2に、変数への値の代入は、関数xStartClock()内で行わなければなりません。そうしないと、秒針のムービークリップインスタンスが、初期位置からスタートしないからです。つまり、ここでは変数の宣言と代入とは別のステートメントとして、分けて処理すればよいということになります(スクリプト02-012)。

Tips 02-017■タイムライン変数とグローバル変数
ヘルプのActionScript 3.0についての説明では、関数の外にvar宣言された変数を「グローバル変数」と呼ぶことがあります(ヘルプ[Programming ActionScript 3.0] > [ActionScript language and syntax] > [Variables]参照)。

けれど、ActionScript 2.0/1.0には、タイムラインには保持されず、しかもどのタイムラインからでもアクセスできる特別な「グローバル変数」というものが存在しました。したがって、ActionScript 2.0/1.0の「グローバル変数」との混同を避け、フレームアクションに宣言した変数がタイムラインに保持されることを明らかにするため、本書では「タイムライン変数」という呼び方を基本にします。


AS1&2 Note 02-006■グローバル変数(_global)
ActionScript 3.0からは、2.0/1.0でグローバル変数を設定した_globalに相当するプロパティは存在しなくなりました


スクリプト02-012■変数を関数の外で宣言したムービークリップシンボルのフレームアクション

// 秒針のムービークリップシンボル
// 第1フレームアクション
var nStart:Number; // 変数宣言(のみ)
addEventListener("click", xStartClock);
function xStartClock(eventObject:MouseEvent):void {
  nStart = getTimer()/1000; // 変数への値の代入
  addEventListener("enterFrame", xRotate);
}
function xRotate(eventObject:Event):void {
  var nSeconds:Number = getTimer()/1000;
  rotation = Math.floor(nSeconds-nStart)*6;
}

フレームアクション(スクリプト02-012)の第1ステートメントで変数をvar宣言しましたので、nStartはタイムライン変数として定義されます。そして、関数xStartClock()の本体で変数nStartに対して行った代入は、タイムライン変数の値として保持され、関数外や他の関数からアクセスすることが可能になります。

[ムービープレビュー]で動作を確認すると、エラーを発生することもなく、秒針のムービークリップインスタンスをクリックしたとき、初期(12時の)位置からアニメーションが始まります。

ローカル変数の意義
ローカル変数を使うと、何がよいのでしょう。あるいは、ローカル変数は、どのようなときに使うべきでしょうか。まず、ローカル変数を使うおもなメリットふたつです。

第1に、メモリに優しいということです。ずっと保持する必要のない値は、ローカル変数に格納することにより、関数の実行が終わればメモリからクリアされます。したがって、メモリの無駄遣いが防げます。第2に、変数値の設定が、関数の外部に影響を及ぼしません。そのため、同じ変数名を使った他の処理に、意図しない影響を与えて、トラブルを発生させることが防げます。

たとえば、テスト用にふたつの関数を定義してみます(スクリプト02-013)。ひとつ目の関数xCountUp()は、呼出すたびにタイムライン変数nの値を1ずつ加算し、加算後の新たな変数値をtrace()関数で[出力]パネルに表示します。ふたつ目の関数xSquare()には、呼出すときに引数として数値をひとつ渡します。すると、その引数の値を2乗して、その計算結果をtrace()関数で[出力]します。

スクリプト02-013■タイムライン変数は他の関数からもアクセスできる

// メインタイムライン
// 第1フレームアクション
var n:Number=0;
function xCountUp():void {
  n+=1;
  trace(n);
}
function xSquare(i:Number):void {
  n = Math.pow(i, 2);
  trace(n);
}

関数の呼出しに引数を渡す場合、すでに述べたとおり(「02-05 イベントリスナーを使う」)それに対応して、関数定義にも引数を宣言する必要があります。引数は呼出しで渡される数と、関数定義に宣言された数とが一致しなければなりません。なお、複数の引数は、呼出しも宣言も、カンマ(,)区切りで指定します。

関数xSquare()には、数値の引数をひとつ渡します。すると、関数定義でも、同じくひとつの変数を宣言して、受取る必要があるのです。引数の名前は、通常の変数と同じく、任意の識別子で指定します。関数定義の丸括弧()内に変数名を記述することが引数の宣言になりますので、varキーワードは記述しません。

数値xのy乗は、Math.pow()メソッドを使って、Math.pow(x, y)で求められます。引数iで受取った数値を2乗するなら、Math.pow(i, 2)となります。上記スクリプト02-013で、引数iの2乗の計算結果を、変数nに代入していることに注意しましょう。

上記フレームアクション(スクリプト02-013)に、関数のテスト用のステートメントを3行追加してみます。タイムライン変数nの値を1ずつ加算する関数xCountUp()の呼出しを、単純に3回繰返します。

xCountUp();
xCountUp();
xCountUp();

関数xCountUp()は、呼出すたびにタイムライン変数nの値を1加算してtrace()関数で[出力]します。したがって、[出力]パネルには、つぎのように1から3までの値が3行にわたって表示されます(図02-024)。

図02-024■関数xCountUp()は1ずつカウントアップした数値を[出力]

[出力]パネルに1から3までの数値が改行して表示される。

さらに、テスト用ステートメントを2行追加します。引数に渡した数値を2乗してtrace()関数で[出力]する関数xSquare()を呼出してから、もう1度関数xCountUp()を実行してみましょう。[出力]結果は、各ステートメントの後にコメント行として加えておきます。

xCountUp();   // 出力: 1
xCountUp();   // 出力: 2
xCountUp();   // 出力: 3
xSquare(4);   // 出力: 16
xCountUp();   // 出力: 17

追加した4行目のステートメントで、関数xSquare()に引数4を渡して呼出しましたので、4の2乗の16が[出力]されました。それに続けて関数xCountUp()を呼出すと、3行目のステートメントで3まで加算されていた変数nに関数xSquare()の結果16が代入されてしまったために、その値に1を加算した17が[出力]されています。

もちろん、それが意図した結果であるなら構いません。しかし、カウントアップする変数nとは別個に、関数xSquare()が引数の2乗の計算結果を格納する変数としてたまたま同じ名前のnを使った場合には、その処理が別の関数xCountUp()のアクセスする変数に意図しない影響を与えてしまったことになります。

このような場合にローカル変数を使うと、他の関数が使う変数にはまったく影響を与えません。関数xSquare()の本体で使う変数名はnのまま、これをvar宣言してローカル変数に変更してみます(スクリプト02-014)。

スクリプト02-014■ローカル変数は他の関数からアクセスできない

// メインタイムライン
// 第1フレームアクション
var n:Number=0;
function xCountUp():void {
  n+=1;
  trace(n);
}
function xSquare(i:Number):void {
  var n:Number = Math.pow(i, 2);   // ローカル変数として宣言
  trace(n);
}

前のスクリプト02-013をテストしたときと同じ、5行のステートメントを加えて[ムービープレビュー]で確かめてみましょう。[出力]結果は、つぎのようになります。

xCountUp();   // 出力: 1
xCountUp();   // 出力: 2
xCountUp();   // 出力: 3
xSquare(4);   // 出力: 16
xCountUp();   // 出力: 4

関数xSquare()を呼出した4行目までの[出力]は、以前と変わりません。しかし、そのあとの5行目のステートメントでもう1度関数xCountUp()を呼出すと、タイムライン変数nの値には影響がなく、3行目に関数を呼出した結果の3に1加算した4が[出力]されました。

このようにタイムライン変数と同じ名前のローカル変数を宣言することも、文法的にはとくに問題はありません。ローカル変数が宣言されていれば、それが優先的にアクセスされるので、同じ名前であってもタイムライン変数には影響を与えません。関数本体にその名前のローカル変数がなければ、タイムライン変数が検索され、その値にアクセスします。

Tips 02-018■関数の引数はローカル変数扱い
関数(function)に渡される引数(パラメータ)は、ローカル変数と同じ扱いになります。したがって、関数の実行後にメモリからクリアされ、タイムラインには保持されません。

ローカル変数として宣言すべきなのは、関数xSquare()の変数nのように、関数実行後に値を保持しなくてよい場合です。関数xSquare()は、毎回引数を受取り、その値を使った計算結果を変数nに格納します。変数nの値は、渡される引数の値によって毎回変わりますので、その結果は保持する必要がありません。

それに対して、関数xCountUp()では、呼出すたびに変数nの値を1ずつ加算し、その値を次回の呼出し時にも参照しなければなりません。したがって、この場合のnは、ローカル変数でなく、タイムライン変数として宣言しておく必要があるのです(表02-006)。

表02-006■タイムライン変数とローカル変数の比較
項目 タイムライン変数 ローカル変数
格納(メモリ)場所 タイムライン(ムービークリップインスタンス) 関数が実行中に有効な特別なオブジェクト
有効な範囲 タイムラインをターゲットにすれば、どこからでも参照できる var宣言をした関数本体からのみ参照できる(関数の外部や他の関数からはアクセスできない)
特徴 タイムラインに値が保持される 関数の実行が終わればメモリからクリアされる
用途 変数値を保持して他の処理や関数からも参照したいとき 関数の実行中のみ変数値を保持して終了後はクリアしたいとき

秒針のムービークリップを回転させたスクリプト02-012でも、関数xRotate()で経過秒数を格納したnSecondsは、ローカル変数として宣言してありました。経過秒数は、関数が呼出されるたびに新しい値を取得して計算し直しますので、以前の値を保持しておく必要はないからです。

しかし、関数xStartClock()でアニメーション開始時の秒数を設定する変数nStartは、その値を後で呼出す関数xRotate()からつねに参照しなければなりませんでした。したがって、ローカル変数でなく、タイムライン変数として宣言し、値を保持する必要があったのです。



02-07 イベントのことをもう少し知ろう
ActionScript 3.0では、イベントの仕組みやその扱いを知ることは、スクリプトを作成する際にきわめて大切なポイントです。その内容は、次章以降に具体的に解説していきます。本節では、その手始めとして、イベントについて少しだけご紹介しておきましょう。

イベントを定数で指定する
前節02-07で秒針がアニメーションするムービーを作成したとき、addEventListener()メソッドの第1引数にはイベントを、"enterFrame"や"click"と文字列で指定しました(スクリプト02-012)。イベントは文字列で記述する以外に、イベントに関わるクラスの定数で指定することもできます。

具体的には、"enterFrame"の替わりにEvent.ENTER_FRAME、"click"に替えてMouseEvent.CLICKという定数でイベントを指定することが可能です。まずは、スクリプト02-012のイベントの指定を、この定数に書替えてみましょう(スクリプト02-015)。

スクリプト02-015■addEventListener()メソッドに定数でイベントを指定する

// 秒針のムービークリップシンボル
// 第1フレームアクション
var nStart:Number;
addEventListener(MouseEvent.CLICK, xStartClock);
function xStartClock(eventObject:MouseEvent):void {
  nStart = getTimer()/1000;
  addEventListener(Event.ENTER_FRAME, xRotate);
}
function xRotate(eventObject:Event):void {
  var nSeconds:Number = getTimer()/1000;
  rotation = Math.floor(nSeconds-nStart)*6;
}


Tips 02-019■Event.ENTER_FRAMEとMouseEvent.CLICK
Event.ENTER_FRAMEMouseEvent.CLICKは、それぞれEventとMoueEventというクラスに定義された定数です。前述(02-01「関数・メソッドを使う」)のとおり、今の段階では「クラス」は、「さまざまな関数(メソッド)やプロパティを集めて、ひとつのカテゴリーとしてまとめたもの」と考えれば結構です。

「定数」は、「予め決まっていて、変更されることのない値」でした(Tips 02-009「小文字で始めない識別子」)。そして、ブログラミングの慣習として「定数はすべて大文字にします」(同前)。Event.ENTER_FRAMEMouseEvent.CLICKも定数ですので、大文字で定義されています。

これらの定数値は、trace()関数で確かめることができます。

trace(Event.ENTER_FRAME);   // 出力: enterFrame
trace(MouseEvent.CLICK);   // 出力: click

結局、定数Event.ENTER_FRAMEには"enterFrame"、MouseEvent.CLICKには"click"という文字列が設定されているのです。しかし、本文で後述するように、addEventListener()メソッドの第1引数にイベントとして直接文字列を指定するより、これらの定数を使う方がスクリプティングとしては確実です。

文字列と比べて、タイプする文字数が増えるだけで、何がよいのかわからないかもしれません。しかし、まず文字数については実際に入力してみればわかるとおり、クラス名(EventやMouseEvent)に続けてドット(.)を打ったときコードヒントが出てくれますので、さほどタイプする数が増えることはありません(図02-025)。それに、定数は正しく入力すると、文字のカラーが(デフォルトではブルーに)変わります。この点は重要です。

図02-025■addEventListener()メソッドに渡すイベントを定数で指定する

コードヒントが表示され、文字のカラーも変わる。

イベントを文字列で指定すると、もちろんコードヒントは出ませんし、文字のカラーも文字列であることを示す(デフォルトではグリーンになる)だけです。たとえば、間違ってイベント名の頭文字を大文字にして"EnterFrame"とタイプしても、スクリプトを書いているときに気づくきっかけがありません。さらに、[ムービープレビュー]を実行しても、文字列で指定したイベントが存在しないことについては、何のエラーも起こりません。

イベントを定数で指定すれば、第1にコードヒントでタイプミスが防げ、第2にシンタックスカラーでActionScriptに定義済みの名前(識別子)であることが確認できます。さらに[ムービープレビュー]を行うと、第3として、指定したイベントが存在しないときには[コンパイルエラー]が発生します。ですから、確実なスクリプトを書くためには、イベントは定数で指定した方がよいでしょう。

イベントオブジェクトをつくるクラス
addEventListener()メソッドの第2引数に指定したリスナー関数が呼出されるとき、イベントオブジェクトというものがそのリスナー関数の引数として渡されました(02-05「イベントリスナーを使う」)。実は、addEventListener()メソッドの第1引数として指定したイベントの定数Event.ENTER_FRAMEMouseEvent.CLICKが属するEventおよびMouseEventは、イベントオブジェクトをつくるクラスでもあります。

秒針のアニメーションのスクリプト02-015を確認してみましょう。イベントにMouseEvent.CLICKを指定すると、リスナー関数はMouseEventクラスのイベントオブジェクトを受取ります。イベントがEvent.ENTER_FRAMEであれば、リスナー関数が受取るのはEventのオブジェクトになります。したがって、リスナー関数の引数のデータ型は、これらのクラスで指定します。このように、イベントを定数で指定すれば、リスナー関数が受取るイベントオブジェクトのデータ型も自動的にわかる訳です。

イベントオブジェクトは、イベントにまつわるさまざまな情報をもっています。その詳しい内容や具体的な利用の仕方については、後の章で解説していきます。ただ、ここでイベントオブジェクトのもつ情報とそのアクセス方法を、ごく簡単にご紹介しておきましょう。

まず、イベントオブジェクトを、そのままtrace()関数で[出力]してみましょう。スクリプト02-015で、マウスクリックのイベントMouseEvent.CLICKに対して指定したリスナー関数xStartClock()に、引数として受取ったeventObjectを[出力]するつぎのステートメントを挿入します(図02-026)。

trace(eventObject);
図02-026■trace()関数でMouseEventイベントオブジェクトを[出力]

ムービークリップインスタンスをクリックすると、[出力]パネルにMouseEventイベントオブジェクトの情報が表示される。

[ムービープレビュー]でムービークリップインスタンスをクリックすると、trace()関数により[出力]パネルにMouseEventイベントオブジェクトの情報が表示されます。その[出力]は、たとえばつぎのような内容になります。表示されたのは、イベントオブジェクトのプロパティです。プロパティの種類や数は、そのイベントオブジェクトを作成するクラスによって異なります。

[MouseEvent type="click" bubbles=true cancelable=false eventPhase=3 localX=-12.5 localY=17.5 stageX=123 stageY=37 relatedObject=null ctrlKey=false altKey=false shiftKey=false delta=0]

たとえば、typeプロパティは、イベントリスナーを登録したイベントを示します。今回の例(スクリプト02-015)であれば、eventObject.typeと記述して参照することができます。また、ctrlKeyaltKeyshiftKeyというプロパティにより、クリックしたときWindowsではそれぞれ[Ctrl][Alt][Shift]キー、Macintoshなら[command][option][shift]キーを押していたかどうか調べることができます。

なお、イベントオブジェクトからアクセスできるプロパティは、この[出力]に表示されたものだけではありません。イベントリスナーに対して、イベントを呼出したインスタンスは、イベントオブジェクトのcurrentTargetプロパティで参照することができます。さらに、インスタンス名はnameプロパティで取得することができますので、trace()関数の引数をつぎのように書替えてみましょう。

trace(eventObject.currentTarget.name);

すると、秒針のムービークリップインスタンスをクリックしたとき、そのインスタンス名であるbar_mcが[出力]パネルに表示されます。


Column 02 Timerクラス
setInterval()関数は、ActionScript 2.0/1.0から同じ仕様で存在します。その意味では、ActionScript 3.0の作法に必ずしも則したものではありません。ActionScript 3.0で、決められた時間間隔の処理を行う場合には、Timerクラスがあります。3.0標準のイベントリスナーを採用し、setInterval()関数より応用の幅も広いといえます。

イベントリスナーを登録するには、インスタンスが必要です。Timerクラスのインスタンスはnew演算子でクラスの特別な関数(コンストラクタ関数)Timerを呼出して生成し、第1引数として実行間隔のミリ秒、およびオプション(省略可)の第2引数に繰返し回数を指定します。インスタンスは変数に保持して、イベントリスナーの登録などに用います。クラスのインスタンス生成については、次章以降に詳しくご説明します。

var インスタンスを格納する変数:Timer = new Timer(実行間隔のミリ秒 [, 繰返し回数]);

第2引数を省略すると、デフォルトの0が設定されます。0は、処理を無限に繰返す指定です。インスタンスを格納した変数を参照して、addEventListener()関数を呼出し、イベントリスナーを登録します。指定間隔で処理を行う場合のイベントは、TimerEvent.TIMER定数で指定します。ただし、イベントリスナーを登録しただけでは、繰返し処理は開始しません。処理を始めるには、インスタンス(を格納した変数)に対して、start()メソッドを呼出す必要があります。

つぎのサンプルスクリプトColumn 02-001は、1秒間隔で3回リスナー関数xTimer()を呼出します。リスナー関数xTimer()は、trace()関数により繰返し処理の実行回数を[出力]パネルに表示します。イベントオブジェクトのcurrentTargetプロパティは、前述のとおりイベントを呼出したインスタンス、今回の場合はTimerインスタンスを参照します。そして、TimerクラスのcurrentCountプロパティは、何回目の繰返し処理かを整数で返します。

スクリプトColumn 02-001■Timerクラスで繰返し処理を行う

// メインタイムライン
// 第1フレームアクション
var myTimer:Timer = new Timer(1000, 3);
myTimer.addEventListener(TimerEvent.TIMER, xTimer);
function xTimer(eventObject:TimerEvent):void {
  trace(eventObject.currentTarget.currentCount);
}
myTimer.start();

[ムービープレビュー]で確認すると、1秒おきに1から3までの数字が[出力]パネルにカウントアップされて、繰返し処理が終わります(図Column 02-001)。

図Column 02-001■Timerインスタンスから呼出された繰返し処理の結果が[出力]される

1秒間隔で繰返し処理の回数が[出力]パネルに表示。

[Prev/Next]


作成者: 野中文雄
作成日: 2007年6月3日


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