サイトトップ

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

ActionScript 3.0 for 3D

□01 ウォーミングアップ − 座標と三角関数と、時々、ベクトル

本書は、ActionScript 3.0の基礎はすでに学ばれた方を対象として、とくにFlash Player 10で備わった3次元座標空間の扱いについて解説します。ActionScript 3.0の基礎として必要なのは、イベントリスナーが使え、その前提として変数や関数(function)を理解していること、および基本的な条件判定の処理が組立てられることなどです。ただ、本論は次章から始めることにして、本章ではそうした基礎のポイントと、まずは2次元平面における座標の捉え方について数学的な知識も含めて確かめておきます。これらの項目の理解にとくに不安がなければ、本章は流し読みするか、つぎの章から読み始めて構いません。


01-01 イベントリスナーと条件判定
ActionScript 3.0では、ユーザーのマウスクリックやキーボード入力などのインタラクティブな操作や、Flash Playerによるスクリーンの描画の更新、外部データの読込みなどの「イベント」(Word 01-001)を捉えて処理が組立てられます。その仕組みは「イベントリスナー」と呼ばれ、EventDispatcher.addEventListener()メソッドでイベントを指定して、処理はリスナー関数として定義します(シンタックス01-001)。

【イベントリスナーの登録と定義】
参照するインスタンス.addEventListener(イベント, リスナー関数)

function リスナー関数(イベントオブジェクト:イベントのデータ型):void {
   ステートメント;
}

EventDispatcher.addEventListener()メソッドの第1引数に指定するイベントは、イベントオブジェクトを定義するクラス(Eventクラスまたはそのサブクラス)の定数を用います。リスナー関数は、引数としてイベントオブジェクトをひとつ受取ります。そのデータ型は、EventDispatcher.addEventListener()メソッドに指定したイベント定数のクラスと一致します。リスナー関数は値を返しませんので、戻り値のデータ型はvoidです。

Word 01-001■イベント
インスタンスに対して予め定められた事象が起こったときに、発せられる信号を「イベント」といいます。「信号」は、「メッセージ」と表現されることもあります。発生するイベントの種類やタイミングは、インスタンスによって異なります。ActionScript 3.0では、あるイベントに対する処理をリスナー関数として定義すると、そのイベントが発生したときに関数を呼出します。

[*筆者用参考] IT用語辞典バイナリ「イベント」、IT用語辞典e-Words「イベント」、akihiro kamijo「EventDispatcherクラス」「イベントリスナーとEventクラス」。


シンタックス01-001■EventDispatcher.addEventListener()メソッド
EventDispatcher.addEventListener()メソッド
文法 addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void
概要 インスタンスが扱うイベントに対して、リスナー関数を登録する。イベントが発生すると、リスナー関数が呼出される。
引数

type:String − リスナー関数を登録するイベントとなる文字列。通常、イベント定数で指定する。

listener:Function − イベントが発生したときに行う処理を定義したリスナー関数。リスナー関数は、引数としてイベントオブジェクトを受取り、戻り値はない。

useCapture:Boolean = false − リスナーがイベントを、そのフローの3つのフェーズのうち、キャプチャフェーズで受取る(true)かどうかを示すブール(論理)値。デフォルトはfalseで、ターゲットおよびバブリングフェーズでイベントを受取る。

priority:int = 0 − リスナーがイベントを受取って処理を行う優先順位となる整数。デフォルトは0で、数値が大きいほど優先度は高い。順位が同じリスナーは、登録順に処理される。

useWeakReference:Boolean = false − リスナー関数に対して弱い参照が設定される(true)かどうかを示すブール(論理)値。弱い参照はガベージコレクションで参照とみなされず、他に参照がなくなれば、メモリは解放される(後述Column 01「ガベージコレクションと弱い参照」参照)。デフォルトはfalseで、強い参照となる。

戻り値 なし。

アニメーションを表現するときに用いる定石のイベントは、DisplayObject.enterFrameイベントです。スクリーンの描画が更新されるたびに発生するイベントで、EventDispatcher.addEventListener()メソッドの第1引数には定数Event.ENTER_FRAMEを指定します。

たとえば、以下のフレームアクション(スクリプト01-001)をメインタイムラインに配置したMovieClipシンボルに設定すると(図01-001)、インスタンスはステージ上を水平スクロールします。インスタンスの位置がステージ右端を超えたら、条件判定により左端に移動してスクロールを繰返します。なお、ステージの右端と幅の変数値は、Stage.stageWidthプロパティで設定しています。

Tips 01-001■Stage.stageWidthとStage.stageHeightプロパティ
ステージ幅はStage.stageWidth、ステージの高さはStage.stageHeightプロパティで調べます。どちらもデータ型はint(整数)です。Stageオブジェクトには、DisplayObject.stageプロパティを通してアクセスする必要があります。インスタンスがStageオブジェクトを頂点とする表示リストに加わっていないと、プロパティ値がnull(値なし)になり、アクセスできませんので注意しましょう。


スクリプト01-001■MovieClipインスタンスを水平スクロールさせる
    // フレームアクション
    // MovieClip: スクロールするシンボル
  1. var nSpeed:Number = 5;
  2. var nStageWidth:int = stage.stageWidth;
  3. var nStageRight:Number = nStageWidth;
  4. addEventListener(Event.ENTER_FRAME, xScroll);
  5. function xScroll(eventObject:Event):void {
  6.   x += nSpeed;
  7.   if (x > nStageRight) {   // ステージ右端を超えたかどうかの条件判定
  8.     x -= nStageWidth;   // ステージ左端に移動
  9.   }
  10. }

図01-001■MovieClipシンボルに水平スクロールするフレームアクションを設定

フレームアクションは、スクリプト用のレイヤーを設け、そこに記述すると管理しやすい。

ifステートメントによる条件判定は、オプションのelse if/elseステートメントも加えると、つぎのように処理を記述します。

【if/else if/elseステートメントによる条件判定の処理】
if (条件1) {
  // 条件1がtrueの場合の処理
} else if (条件2) {
  // 条件1がfalseで条件2がtrueの場合の処理
……
} else if (条件n) {
  // 条件1〜条件n-1がfalseで条件nがtrueの場合の処理
} else {
  // すべての条件がfalseの場合の処理
}

条件は上から順に判定され、trueと評価されたらそのステートメントを実行し、残りのelse ifelseステートメントは処理されません。ちょうど勝抜けのクイズと同じです。1問正解したら、解答者の席から抜け、後の問題には解答しません。ifまたはelse ifの条件を初めから順に判定し、ひとつtrueと評価されたらそのステートメントを処理して抜け、後の条件は判定しないのです。

05-003
if/else if/elseの条件判定は、勝抜けクイズと同じ。ひとつでもtrueと評価されたら、後の条件は判定しない。


Tips 01-002■if条件の順序
if/else if/elseステートメントによる条件判定が勝抜けだとすると、条件の順序がとても大切になりす。

たとえば、みかんを大きさによって仕分ける機械があります。仕組みはとても簡単で、コンベヤーの先に穴が3つ開いています。その大きさは、手前からS玉、M玉、L玉の順です。小さいみかんは、まず手前のS玉の穴に落ちます。つぎに、そこを通り過ぎたみかんのうち、中くらいのものがM玉の穴に落ちます。そして、残ったみかんがL玉の穴に落ちることになります。これでみかんの選別ができる訳で、間違っても一番手前にL玉の穴を開けてはいけません。

05-005
穴の大きさは、手前からS、M、Lでないとダメ。

穴つまり条件の順所を正しく設定すれば、その判別の処理がシンプルになります。また、当てはまる可能性の高い条件をできるだけ手前にもってきて、初めの勝抜けをより多くすることにより、処理の最適化をはかることもできるのです。


01-02 本章のお題 − マウスポインタにインスタンスを追随させる
この項以降、本章では同じ動きのムービーをスクリプトで書きます。それは、マウスポインタの後を減速(「イーズアウト」と呼ばれる)しながら追いかけるインスタンスのアニメーションです(図01-002)。座標をさまざまな考え方で捉えることが目的です。

図01-002■マウスポインタをイーズアウトしながら追いかける

インスタンスがマウスポインタの後を、イーズアウトしながら追いかける。

インスタンスのxy座標は、DisplayObject.xDisplayObject.yプロパティで設定できます。また、マウスポインタの座標は、DisplayObject.mouseXおよびDisplayObject.mouseYプロパティで調べます。するとたとえば、インスタンスの水平座標をマウスポインタの水平座標と一致させるには、ついつぎのステートメントのように書きたくなります。

x = mouseX;

けれど、これでは意図した結果になりません。座標を扱うときには、その座標空間を知っておく必要があります。この場合は、とくに原点(0, 0)の位置が問題です。

インスタンスの位置座標は、それが配置された親インスタンス(タイムライン)の基準点を原点とします。つまり、親から見た座標です。ところが、マウスポインタの座標は参照した(ターゲットの)インスタンスの基準点が原点となります。すなわち、自分から見た座標ということです(シンタックス01-002)。

シンタックス01-002■インスタンスの位置座標とマウスポインタの座標
プロパティ プロパティ値 座標の捉え方
データ型
DisplayObject.x 親(DisplayObjectContainer)インスタンスの基準点から見た参照する(DisplayObject)インスタンスの水平座標。 地動説
Number
DisplayObject.y 親(DisplayObjectContainer)インスタンスの基準点から見た参照する(DisplayObject)インスタンスの垂直座標。
Number
DisplayObject.mouseX 参照する(DisplayObject)インスタンスの基準点から見たマウスポインタの水平座標。 天動説
Number
DisplayObject.mouseY 参照する(DisplayObject)インスタンスの基準点から見たマウスポインタの垂直座標。
Number

インスタンスの位置座標は、宇宙(あるいは太陽系)の中心から地球の位置を測るのと同じで、いわば「地動説」の捉え方です。けれども、マウスポインタの座標は、インスタンス自身を中心に決めます。これは、地球を中心に太陽が東から昇って西に沈むと考えるのと同じ「天動説」です。原点すなわち基準とする座標空間が異なりますので、プロパティの値を単純にイコール(=)で結んで代入しても、インスタンスは意図した位置に動きません。

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

そこで、正しい動作をさせるには、代入式両辺の座標空間を一致させればよいということになります。以下のステートメントがそれです。代入式右辺は天動説の基準です。左辺が一見地動説のままで、基準が変わっていないように思えるかもしれません。しかし、代入は加算後代入演算子+=で行われています。したがって、自分から見た目標値を足し込んで、差を埋めるという天動説の処理に変わったのです。

x += mouseX;

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

以上の知識をもとに、お題のスクリプトを作成します。以下のフレームアクション(スクリプト01-002)をMovieClipシンボルに設定すると、インスタンスがマウスポインタを減速しながら追いかけます。ただし、代入式の右辺に変数値が乗じられています。これは、目標の位置に減速しながら近づく、いわゆる「イーズアウト」のアニメーションをさせるための調整係数です。

スクリプト01-002■インスタンスをマウスポインタに追随させる
    // フレームアクション
    // MovieClip: マウスポインタに追随させるシンボル
  1. var nDeceleration:Number = 0.2;
  2. addEventListener(Event.ENTER_FRAME, xFollowMouse);
  3. function xFollowMouse(eventObject:Event):void {
  4.   x += mouseX * nDeceleration;
  5.   y += mouseY * nDeceleration;
  6. }

目標値との差を埋め合わせれば、一気に目標に到達します。そうでなく、その差の値に0から1の間の減速率となる係数を掛合わせてから加えれば、目標には近づきますが、まだ届きません。他方、差が縮まるにつれ、足し込む値は減っていきます。したがって、減速しながら目標値に近づくというアニメーションができる訳です。

イーズアウトの処理を公式風に表現したのが、下表01-001です。自分から見た目標との差は、マウスポインタの座標のように直ちには得られないこともあるでしょう。その場合には、目標値から自分の値を引き算して、自分から見た値を求めます。

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

または、

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

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


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

今回のお題については、前掲スクリプト01-002が処理としてはシンプルでしょう。けれど本章では、他にもいくつかの座標の捉え方を紹介します。それらを理解しておくことは、3次元座標を扱うときの基礎となります。また、アニメーションの内容や仕様が変われば、有利な手法も違ってくるからです。


01-03 三角関数で距離と角度から座標を捉える − 極座標
2次元平面上の位置の表し方は、x軸とy軸が直角に交わる「直交座標」で示す以外にもあります。たとえばレーダーは、「北東1kmの地点」というように、方向と距離で目的地を指定します(図01-002)。方向とはつまり角度で、北東はx軸方向となる東に対して、反時計回りに45度を意味します。

図01-003■レーダーは位置を角度と方向で指定する

北東はx軸方向となる東に対して、反時計回りに45度を意味する。

このレーダーのように、2次元平面上の位置を距離rと角度θで示した座標(r, θ)は「極座標」と呼ばれます。もっとも、Flashを始め、多くの座標計算ではxy座標の方が扱いやすいでしょう。そこで、極座標を直交座標に変換する関数が必要になります。それが三角関数です。

もう少し厳密にいうと、三角関数は距離が1の場合について、角度θのxy座標を(cosθ, sinθ)と定めます(図01-004)。それがわかれば、距離rの場合の座標は簡単です。xyの各座標値に単純にrを掛合わせて、(r cosθ, r sinθ)とすればよいからです。なお、cosを「余弦」、sinは「正弦」と呼ばれることもあります。

【極座標から直交座標への変換】
極座標(r, θ) → 直交座標(r cosθ, r sinθ)
図01-004■三角関数は距離1の場合の角度θに対するxy座標を定める

距離が1で角度θの位置を示すxy座標は(cosθ, sinθ)。


距離1で角度θのxy座標が(cosθ, sinθ)になるのは、そもそも三角関数をそのように定義したから。

Mathクラスの三角関数に関わるメソッドを使うときに注意しなければならないのは、角度の単位が度数でなく「ラジアン」だということです。ラジアンもまた、sinやcos関数と同じく、半径1の円で定義されます。ラジアン角θは、半径1の円弧の長さで表されるのです(図01-005)。したがって、360°は2πラジアンとなります。なお、半径1の円を「単位円」と呼びます。

図01-005■ラジアンは角度を単位円の弧の長さで表す

360°= 2πラジアン
単位円の弧APの長さが、ラジアンを単位としたθの角度となる。

もっとも、ActionScriptでは、DisplayObject.rotationプロパティなど、角度を度数で扱うことも少なくありません。度数とラジアン値は、簡単な比例計算で、つぎのように換算することができます。

【度数とラジアンの換算式】
度数 = ラジアン×(180/π)
ラジアン = 度数×(π/180)

これで、スクリプトを書く準備が整いました。位置を極座標で扱うには、距離と角度を知る必要があります。まず2点間の距離は、三平方の定理を使って求めます。これは直角三角形の斜辺の2乗は、他の2辺の2乗の和に等しいという関係です。2点の座標をそれぞれ(x1, y1)および(x2, y2)とすると、三平方の定理より2点間の距離lはつぎのように導かれます(図01-006)。

図01-006■三平方の定理により2点間の距離を求める

l = √
_______________
(x1 - x2)2 + (y1 - y2)2
距離つまり斜辺の長さは、他の2辺の2乗を足して、その平方根として導かれる(ただし、値は正)。

具体的に、インスタンスから見たマウスポインタの座標(mouseX, mouseY)までの距離は、起点がインスタンスの基準点(0, 0)ですので、2点間の差を取る必要はありません。累乗はMath.pow()、平方根はMath.sqrt()メソッドで求めます(シンタックス01-003)。なお、Mathクラスは、基本的に静的なメソッドと定数で構成されます。つまり、Mathクラスを直接参照してメソッドや定数を操作します。

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

つぎに、マウスポインタの座標(mouseX, mouseY)から角度を調べます。そのためのメソッドがMath.atan2()です(シンタックス01-003)。注意することは、ふたつの引数の順序がy座標、x座標となることです。

var nRadian:Number = Math.atan2(mouseY, mouseX);

Tips 01-003■Math.atan2()メソッドは三角関数?
atan2という関数は、数学的な三角関数ではありません。このメソッド名の由来は、tanの逆関数(逆三角関数)を示すtan-1でアークタンジェントと呼ばれます(Mathクラスには、Math.atan()メソッドとして備わっています)。このtan-1はtanの値(y/x)を引数に渡すと、その角度を返します。ただし、単位円の半分、つまり180度(±π/2)の範囲でしか定義されていません。

-π/2 < tan-1x < π/2

そこで、引数をy座標とx座標のふたつに増やすことにより、全周360度(±π)の範囲で角度を得られるようにしたプログラミング上のメソッドがMath.atan2()なのです。



Math.atan2()は、座標から角度が求められる。「三角関数?!」などと難しく考える必要はなし。引数の順序には注意。


シンタックス01-003■Mathクラスでよく使われる座標と角度に関わるメソッド
Math.cos()メソッド
文法 Math.cos(angleRadians:Number):Number
概要 [静的] 指定されたラジアン角の数値からcos(余弦)の値を返す。
引数 angleRadians:Number ― ラジアンで角度を示す数値。
戻り値 指定された角度のcos(余弦)を表す数値。値の範囲は±1。
Math.sin()メソッド
文法 Math.sin(angleRadians:Number):Number
概要 [静的] 指定されたラジアン角の数値からsin(正弦)の値を返す。
引数 angleRadians:Number ― ラジアンで角度を示す数値。
戻り値 指定された角度のsin(正弦)を表す数値。値の範囲は±1。
Math.atan2()メソッド
文法 Math.atan2(y:Number, x:Number):Number
概要 [静的] 指定されたxy座標値から角度をラジアン値で返す。
引数

y:Number ― 角度を調べる位置のy座標値。

x:Number ― 角度を調べる位置のx座標値。

戻り値 指定された位置座標の角度を表すラジアン値。値の範囲は±π。
Math.pow()メソッド
文法 Math.pow(base:Number, power:Number):Number
概要 [静的] 基数を指数で累乗した数値が返される。
引数

base:Number ― 基数すなわち累乗される数値。

power:Number ― 指数すなわち累乗する数値。

戻り値 指定された基数を指数で累乗した数値。
Math.sqrt()メソッド
文法 Math.sqrt(value:Number):Number
概要 [静的] 指定された数値の平方根が返される。
引数

value:Number ― 平方根を計算する0以上の数値。

戻り値 引数の平方根、ただし、マイナスの値が指定されるとNaNを返す。

さて、距離と角度が求められましたので、あとは前述のとおり、極座標(距離, 角度)から直交座標(x, y)を導くだけです。この考え方でお題をフレームアクションにしたのが、以下のスクリプト01-003です。なお、cosとsinは、Math.cos()およびMath.sin()メソッドにより得られます(シンタックス01-003)。

スクリプト01-003■インスタンスの座標を三角関数で指定する
    // フレームアクション
    // MovieClip: マウスポインタに追随させるシンボル
  1. var nDeceleration:Number = 0.2;
  2. addEventListener(Event.ENTER_FRAME, xFollowMouse);
  3. function xFollowMouse(eventObject:Event):void {
  4.   var nX:Number = mouseX;
  5.   var nY:Number = mouseY;
  6.   var nDistance:Number = Math.sqrt(Math.pow(nX, 2) + Math.pow(nY, 2));
  7.   var nRadian:Number = Math.atan2(nY, nX);
  8.   x += nDistance * Math.cos(nRadian) * nDeceleration;
  9.   y += nDistance * Math.sin(nRadian) * nDeceleration;
  10. }

すでに述べたとおり、今回のお題では、このスクリプト01-003は前掲スクリプト01-002と比べて、とくにお得な点はありません。けれど、アニメーションの内容がたとえば砲弾を撃つとか、飛行機や宇宙船を操縦するといった場合には、方向と速さ(フレーム当たりの移動距離)で動きが指定できると便利でしょう。


01-04 Pointクラスで座標を位置ベクトルとして扱う
Pointクラスは、2次元平面のxy座標を管理します。加えて、座標を位置ベクトルとして扱い、簡単なベクトル演算もできます。今回のお題を使って、その例をいくつかご紹介します。

Pointインスタンスは、ActionScript 3.0の原則どおりnew演算子でコンストラクタPoint()を呼出して生成します。ふたつの引数はxy座標値で、デフォルト値はともに0です。インスタンスのxy座標値は、Point.xおよびPoint.yプロパティで取得・設定します(シンタックス01-004)。

var 変数:Point = new Point(x座標値, y座標値)

また、Pointクラスの静的メソッドPoint.polar()を使うと、極座標を指定してインスタンスが生成できます。なお、Pointインスタンスの原点から座標までの距離は、Point.lengthプロパティで調べられます(シンタックス01-004)。

var 変数:Point = Point.polar(距離, 角度)
シンタックス01-004■Pointインスタンスの生成と座標値・長さおよび極座標
Point()コンストラクタ
文法 Point(x:Number = 0, y:Number = 0)
概要 Pointインスタンスを生成する。
引数

x:Number = 0 ― インスタンスの水平座標値。デフォルト値は0。

y:Number = 0 ― インスタンスの垂直座標値。デフォルト値は0。

Point.xプロパティ
文法 x:Number
プロパティ値 Pointインスタンスの水平座標値。Point()コンストラクタでこの値を指定する引数なしに生成されたインスタンスには、デフォルト値として0が設定される。
Point.yプロパティ
文法 y:Number
プロパティ値 Pointインスタンスの垂直座標値。Point()コンストラクタでこの値を指定する引数なしに生成されたインスタンスには、デフォルト値として0が設定される。
Point.lengthプロパティ
文法 length:Number
プロパティ値 [読取り専用] Pointインスタンスの座標の原点(0, 0)からの距離、つまり位置ベクトルとしての大きさ(長さ)を示す数値。
Point.polar()メソッド
文法 Point.polar(distance:Number, angleRadians:Number):Point
概要 [静的] 引数に極座標となる原点からの距離と角度を指定すると、その位置の直交座標のPointインスタンスを返す。
引数

distance:Number ― 求める位置の原点からの距離を示す数値。

angleRadians:Number ― 求める位置と原点とを結ぶ線分が、水平軸と成す角度を示すラジアン値。

戻り値 引数の極座標値、すなわち原点からの距離と角度で示された位置のxy座標をもつPointインスタンス。

前掲スクリプト01-003と同じ極座標で捉えて、Pointクラスにより処理するフレームアクションが以下のスクリプト01-004です。マウスポインタの座標が与えられたPointインスタンス(mousePoint)からPoint.lengthプロパティで距離を調べ、Math.atan2()メソッドにより角度を求めています。そのうえで、Point.polarメソッドで、その距離と角度の極座標から、直交座標のPointインスタンスを生成しました。

スクリプト01-004■インスタンスの座標をPoint.polar()メソッドで求める
    // フレームアクション
    // MovieClip: マウスポインタに追随させるシンボル
  1. var nDeceleration:Number = 0.2;
  2. addEventListener(Event.ENTER_FRAME, xFollowMouse);
  3. function xFollowMouse(eventObject:Event):void {
  4.   var nX:Number = mouseX;
  5.   var nY:Number = mouseY;
  6.   var mousePoint:Point = new Point(nX, nY);
  7.   var nRadian:Number = Math.atan2(nY, nX);
  8.   var myPoint:Point = Point.polar(mousePoint.length * nDeceleration, nRadian);
  9.   x += myPoint.x;
  10.   y += myPoint.y;
  11. }

ベクトルというのは、大きさと方向をもった値です。すると今回のお題では、わざわざ角度を求める必要はなく、位置ベクトルの方向はそのままで大きさだけ変えれば済みそうです。Pointインスタンスのベクトルとしての大きさは、Point.normalize()メソッドで設定できます(シンタックス01-005)。

Pointインスタンス.normalize(大きさ)
シンタックス01-005■Point.normalize()メソッド
Point.normalize()メソッド
文法 normalize(distance:Number):void
概要 Pointインスタンスのxy座標の原点(0, 0)からの距離が、引数て指定された数値になるように大きさを変える。
引数

distance:Number ― 大きさを指定する数値。マイナスの値を渡すと、方向が逆転する。

戻り値 なし。

マウスポインタの座標が与えられたPointインスタンスに対してPoint.normalize()メソッドを呼出し、その大きさを減速率の乗じられた値に変えているのが、つぎのスクリプト01-005です。

スクリプト01-005■Pointインスタンスの大きさを変えて位置の指定
    // フレームアクション
    // MovieClip: マウスポインタに追随させるシンボル
  1. var nDeceleration:Number = 0.2;
  2. addEventListener(Event.ENTER_FRAME, xFollowMouse);
  3. function xFollowMouse(eventObject:Event):void {
  4.   var myPoint:Point = new Point(mouseX, mouseY);
  5.   myPoint.normalize(myPoint.length * nDeceleration);
  6.   x += myPoint.x;
  7.   y += myPoint.y;
  8. }

Maniac! 01-001■ベクトルの正規化(normalize)
ベクトルの演算で"normalize"は、「正規化」と訳されます。数学では、大きさが1のベクトルにすることを正規化といいます。大きさが1のベクトルは「単位ベクトル」と呼ばれます。実際、3次元ベクトルを扱うVector3Dクラス(後述06「3次元空間の座標を扱う − Vector3Dクラス」)では、Vector3D.normalize()メソッドは引数なしにインスタンスを単位ベクトルにします。

その点では、引数で大きさを指定するPoint.normalize()メソッドは、少し変わっているといえます。

[*筆者用参考]「ベクトル」、「ベクトル其の弐」。

イーズアウトの減速率は、目標値を割引く比率です。そして、Pointクラスには、2点を結ぶ線分上で指定した比率の位置座標をPointインスタンスで返す静的メソッドPoint.interpolate()が備わっています(シンタックス01-006)。ふたつのPointインスタンスをそれぞれ変数firstPointとsecondPointとしたとき、2点を結ぶ線分上でsecondPointから比率interpolationの位置の座標を示すPointインスタンスは、つぎのように求められます(図01-007)。

var myPoint:Point = Point.interpolate(firstPoint, secondPoint, interpolation)
図01-007■2点の座標を補間するPoint.interpolate()メソッド

2点が結ばれた線分上の指定した比率の位置座標を求める。このような演算は、「補間」(interpolation)と呼ばれる。

シンタックス01-006■Point.interpolate()メソッド
Point.interpolate()メソッド
文法 Point.interpolate(firstPoint:Point, secondPoint:Point, interpolation:Number):Point
概要 [静的] 指定したふたつのPointインスタンスの座標を結ぶ線分上で、指定した比率の位置にある中間座標をPointインスタンスで返す。
引数

firstPoint:Point ― 中間の座標を計算する2点のうちのひとつ目の座標のPointインスタンス。

secondPoint:Point ― 中間の座標を計算する2点のうちのふたつ目の座標のPointインスタンス。

interpolation:Number ― 第1引数と第2引数のふたつのPointインスタンスの座標を結ぶ線分上で、線分の長さを1としたとき、第2引数の座標から中間座標までの比率となる数値。返される中間座標のPointインスタンスは、この引数値が0のとき第2引数の座標となり、値が1であれば第1引数の座標になる。

戻り値 第1および第2引数に指定されたふたつのPointインスタンスの座標を結ぶ線分上で、線分の長さを1としたとき、第2引数の座標から第3引数の比率の位置にある中間座標のPointインスタンス。

Point.interpolate()メソッドにより、マウスポインタの座標とインスタンスの基準点の中間座標を求めたのが、以下のスクリプト01-006です。インスタンスの基準点は、座標(0, 0)です。インスタンスの基準点からマウスポインタの座標に向かって、減速率の比率で求めた中間座標にインスタンスを移動すれば、イーズアウトのアニメーションになります。

スクリプト01-006■Point.interpolate()メソッドで2点の中間座標として移動位置を求める
    // フレームアクション
    // MovieClip: マウスポインタに追随させるシンボル
  1. var nDeceleration:Number = 0.2;
  2. var myPoint:Point = new Point();
  3. addEventListener(Event.ENTER_FRAME, xFollowMouse);
  4. function xFollowMouse(eventObject:Event):void {
  5.   var mouse:Point = new Point(mouseX, mouseY);
  6.   var myPoint:Point = Point.interpolate(mouse, myPoint, nDeceleration);
  7.   x += myPoint.x;
  8.   y += myPoint.y;
  9. }

Tips 01-004■Point.interpolate()メソッドの第3引数の比率
Point.interpolate()メソッドの第3引数には、比率として1より大きい値や負の数値も指定できます。その場合、返されるPointインスタンスの座標は、2点の外側に取られることになります。

座標空間と座標の捉え方、それらを扱うためのクラスやプロパティ、メソッドについて、数学的な基礎にも触れながら簡単に解説しました。アニメーションの内容やスクリプトの仕様から、適切な手法を選ぶことが大切です。次章からは、おもにFlash Player 10以降で備わった3次元座標空間を扱っていきます。その場合にも、これまでご説明した2次元平面の考え方が出発点となります。


Column 01 ガベージコレクションと弱い参照
スクリプトで生成したオブジェクトが、もはや要らなくなってもそのまま残っていたら、無駄にメモリが食われます。最悪、メモリが足りなくなるかもしれません。ActionScript 3.0では、「ガベージコレクション」(garbage collection)という仕組みによって、不要なオブジェクトを消して、メモリを解放します。また、このガベージコレクションに絡んでEventDispatcher.addEventListener()メソッド(前掲シンタックス01-001)で用いられる「弱い参照」についてもご説明しましょう。

ガベージコレクション
変数やプロパティにインスタンスを代入すると、そのオブジェクトへの参照が与えられます。あるいは、インスタンスにイベントリスナーを登録すれば、リスナー関数が参照されることになります。数多くのインスタンスがさまざまな参照を受取り、いくつもの参照が与えられるオブジェクトも少なくありません。ガベージコレクションは、参照がひとつもなくなったオブジェクトを自動的にメモリから消し去る技術です。

これは食堂や居酒屋で、お客さんが席を立って帰ったらテーブルの上をきれいにするというのに似ています。グループ客の場合には、その全員が食事を終えて誰も席にいなくなったら片づけを行います。逆に、片づけてほしいなら、さっさと席を離れればよいということです。

[イラスト] 席に誰もいなくなったら、テーブルの上をきれいにする。

つまり、要らなくなったインスタンスへの参照は、すべてなくしてしまう必要があります。変数やプロパティの場合には、他のオブジェクトへの参照で上書きするか、nullを代入します。イベントリスナーは、DisplayObject.removeEventListener()メソッドで登録を削除します。

ひとつ気をつけなければならないのは、ガベージコレクションは重い処理なので、あるインスタンスへの参照をすべて消しても、直ちにはメモリが解放されないということです。したがって、そのオブジェクトがたとえば連続した処理を行っていれば、参照をすべて破棄した後もそのままその処理が続けられてしまいます。ガベージコレクションをスクリプトで明示的に実行する機能は用意されていません。ですから、参照を消すのとは別に、停止などの後処理は行っておく必要があります。

Tips 01-005■デバッグ時にガベージコレクションを実行するメソッド
デバッグのときにかぎり、ガベージコレクションを明示的に実行するメソッドとしてSystem.gc()があります。

シンタックス01-007■System.gc()メソッド
System.gc()メソッド
文法 System.gc():void
概要 ガベージコレクションの処理を、直ちに実行させる。ただし、デバッグ時(Flash Playerデバッグ版)のみ。
引数 なし。
戻り値 なし。

弱い参照
EventDispatcher.addEventListener()メソッドの最後の(第5)引数は、弱い参照を使うかどうかブール(論理)値で指定します(前掲シンタックス01-001)。弱い参照は、ガベージコレクションのとき、参照とはみなされません。したがって、イベントリスナーの参照以外なくなってしまうと、メモリから消し去られることになります。

参照するインスタンス.addEventListener(イベント, リスナー関数, キャプチャフェーズ, 優先度, 弱い参照)

もっとも、前述のとおり、オブジェクトへの参照がなくなったからといって、ただちにガベージコレクションが働く訳ではありません。とくに、メモリに余裕があるときは、オブジェクトが残り続けることもよくあります。ですから、引数で弱い参照を(trueに)指定したとしても、それに頼り切らずに、要らなくなったイベントリスナーはDisplayObject.removeEventListener()メソッドで削除するべきでしょう。

また、他の人が書いたプログラムを利用するときは、イベントリスナーに弱い参照を使っているかどうかは簡単にはわかりません。その場合、オブジェクトへの参照を別に保持しておかないと、動作が止まってしまうということもありえますので注意しましょう(例としては、FumioNonaka.com「Tweenのアニメーションが途中で止まる」<http://fumiononaka.com/TechNotes/Flash/FN0711001.html>があります)。

以下のフレームアクション(スクリプト01-007)は、EventDispatcher.addEventListener()メソッドを呼出して、DisplayObject.enterFrameイベント(定数Event.ENTER_FRAME)にリスナー関数を登録し、最後の(第5)引数には弱い参照(true)を指定しました。リスナーには名前のない(匿名)関数を渡したので、他には参照はありません。

別途Timerインスタンスを用いて、参照のないTextFieldインスタンスを大量に生成し、ガベージコレクションが働くよう促します。DisplayObject.enterFrameイベントのリスナー関数は、System.totalMemoryプロパティで調べたFlash Playerの使用メモリを[出力]パネルに表示します。

スクリプト01-007■EventDispatcher.addEventListener()メソッドに弱い参照を指定
    // フレームアクション
  1. var myTimer:Timer = new Timer(1);
  2. addEventListener(Event.ENTER_FRAME,
  3.   function (eventObject:Event):void {
  4.     trace(System.totalMemory);
  5.   }, false, 0, true);   // 弱い参照を指定
  6. myTimer.addEventListener(TimerEvent.TIMER, xTest);
  7. myTimer.start();
  8. function xTest(eventObject:Event):void {
  9.   new TextField();
  10. }

[ムービープレビュー]で試すと、[出力]されるメモリの使用量がある程度増えたときに、表示は止まります。弱い参照しか与えられなかったイベントリスナーが、ガベージコレクションにより消去されたからです。

Maniac! 01-002■ガベージコレクションと弱い参照についての参考ドキュメント
ガベージコレクションと弱い参照については、[ヘルプ]の[ActionScript 3.0 のプログラミング] > [イベント処理] > [イベントリスナー](「イベントリスナーの管理」の項)のほか、Adobeの上条晃宏氏によるblog記事「イベントリスナ(AS3)とガーベジコレクション」が参考になります。

また英文では、Grant Skinner氏が3回にわたってガベージコレクションについてのblog記事「AS3: Resource Management pt 1」「AS3: Resource Management pt 2」「AS3: Resource Management pt 3」を書かれています。その中には、正規サポートはされないものの、ガベージコレクションを実行する裏技も紹介されています。

[*筆者用参考] Wikipedia「ガベージコレクション」、trick7「SWF ファイルをアンロードする前にすべきこと(と、Garbage Collection ことはじめ)」、My life as an APE「ActionScript最適化 多分その1 ガベージコレクタを攻略する」、@IT「Javaパフォーマンスチューニング」。

[Main/Next]


作成者: 野中文雄
変更日: 2009年10月23日 イラストを1点追加。
変更日: 2009年9月12日 EventDispatcher.addEventListener()メソッドのクラス名が間違っていたのを修正。また、スクリプトに行番号を追加。
作成日: 2009年9月1日


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