ActionScript 3.0で始めるオブジェクト指向スクリプティング

第22回MovieClipシンボルにクラスを定義する

第18回から前回の第21回にかけて、カスタムクラスの定義の仕方を学んだ。今回は、クラスをMovieClipシンボルに対して定義してみたい。

MovieClipシンボルにクラスを定義すると、シンボルのタイムラインや配置されたエレメントなどのアセットが、そのクラスに関連づけられる。すると、MovieClipシンボルを[ライブラリ]からステージにドラッグ&ドロップするだけで、クラスに定義した動作が実現されることになる。コンポーネントのようなパーツをつくることも可能だ。

MovieClipシンボルに設定するスクリプトとしては、第17回3D風に回転するアニメーションで作成したフレームアクションを利用しよう図1⁠。サンプルファイルは、第17回の3ページからダウンロードできる。

図1 3D風に回転するアニメーション
図1 3D風に回転するアニメーション

MovieClipシンボルに設定するクラス

3D風に回転させるMovieClipシンボルに定義するカスタムクラスは、楕円運動を意味するEllipticMotionと名づけよう。しかし、いきなりアニメーションの動作を書き始める前に、MovieClipシンボルに設定するクラス定義が最低限満たすべきスタイルを確かめておきたい。

MovieClipシンボルに設定するクラス定義の基本スタイル
// ActionScript 3.0クラス定義ファイル: クラス名.as
package [パッケージ名]{
  import flash.display.MovieClip;
  // 他のimport宣言
  public class クラス名 extends MovieClip {
    // クラス定義の本体
  }
}

第1に、MovieClipシンボルに設定するクラスには、アクセス制御の属性としてpublicを指定しなければならない[1]⁠。MovieClipインスタンスは、配置されたタイムライン上で操作される。したがって、タイムラインからのアクセスを許す必要があるからだ。

第2に、MovieClipシンボルに設定するクラスには、基本的にextends定義キーワードでMovieClipクラスを継承させる。⁠継承」とは、⁠スーパークラスの機能(プロパティやメソッド)がサブクラスで使えるプログラミングの仕組み」だった(第9回「座標の天動説と地動説」インスタンスとマウスポインタの座標⁠。

MovieClipシンボルにカスタムクラスを設定すると、そのインスタンスはカスタムクラスのオブジェクトとなり、そのままではMovieClipクラスのプロパティやメソッドがまったく使えない。たとえば、xy座標を動かすことさえできないのだ。よって、MovieClipクラス(およびそのスーパークラス)の機能を使えるようにするためには、その継承が必要になる。

第3は、import宣言だ。もっとも、このステートメントの記述が必要なのは、MovieClipシンボルに設定するクラスにかぎったことではない。ActionScriptに定義済みのクラスのほとんどは、パッケージに属している。たとえば、MovieClipクラスなら、[ActionScript 3.0言語およびコンポーネントリファレンス]の説明冒頭の記載から、パッケージはflash.displayだとわかる図2⁠。

図2 MovieClipクラスのパッケージ
図2 MovieClipクラスのパッケージ

すると、クラス名の前にパッケージを添えたflash.display.MovieClipがクラスの正式な名前となる。クラスの正式な名前は「完全修飾クラス名」と呼ばれる。カスタムクラスの定義でMovieClipクラスを用いるときには、importディレクティブで完全修飾クラス名を宣言する必要があるのだ。

では、まずは空のカスタムクラスEllipticMotionを定義して、MovieClipシンボルにそのクラスを設定してみよう。EllipticMotionクラスの定義は、以下のようになる。コンストラクタメソッド内には、確認のためにtrace()ステートメントを記述して、クラスが設定されたインスタンスのxy座標値とインスタンス自身の参照を[出力]することにした。なお、クラス定義ファイルは、それを設定するMovieClipシンボルが[ライブラリ]に納められているFlashムービー(FLA)ファイルと同階層に保存する。

// ActionScript 3.0クラス定義ファイル: EllipticMotion.as
package {
  import flash.display.MovieClip;
  public class EllipticMotion extends MovieClip {
    public function EllipticMotion() {
      trace(x, y, this);
    }
  }
}

このままではまだクラスEllipticMotionとMovieClipシンボルの間には何の関わりもない。このふたつを関連づけるには、[ライブラリ]のMovieClipシンボルを選び、[シンボルプロパティ]のダイアログボックスで[クラス]フィールドにカスタムクラス名"EllipticMotion"を入力する必要がある[2]⁠。

図3 [シンボルプロパティ]のダイアログボックスで[クラス]を設定
図3 [シンボルプロパティ]のダイアログボックスで[クラス]を設定

クラスの設定されたMovieClipシンボルのインスタンスは、スクリプトで動的につくることにしよう。ステージには何も置かず、MovieClipシンボルが納められたFlashムービー(FLA)ファイルに以下のフレームアクションを記述する。このステートメントは、クラスEllipticMotionのコンストラクタメソッドを呼出し、インスタンスを生成している。

var my_mc:EllipticMotion = new EllipticMotion();

[ムービープレビュー]を行うと、コンストラクタ内でtrace()関数に渡したインスタンスのDisplayObject.xとDisplayObject.yプロパティ値、ならびにインスタンスのthis参照が[出力]パネルに表示される。しかし、インスタンスがステージに表れない図4⁠。

図4 xy座標値と参照は[出力]されてもインスタンスがステージに表示されない
図4 xy座標値と参照は[出力]されてもインスタンスがステージに表示されない

これはインスタンスを作成しただけではまだそれはメモリ上の存在で、いずれのタイムラインにも属していないからだ。Flash Playerのステージは、Stageインスタンスを頂点としたインスタンスのツリー構造でできあがっている図5⁠。ステージ上に表示されるためには、いずれかのインスタンスの子として、このツリー構造に加わらなければならない。

そのためには、親とすべきインスタンスを参照して、DisplayObjectContainer.addChild()メソッドを呼出し、その引数に子として追加したいインスタンスを渡す。なお、そうして加えられた子インスタンスは、親インスタンスの「表示リスト」という容れ物に納められる[3]⁠。

親インスタンス.addChild(子インスタンス)
図5 ステージはStageインスタンスを頂点としたツリー構造
図5 ステージはStageインスタンスを頂点としたツリー構造

では、フレームアクションにDisplayObjectContainer.addChild()メソッドの呼出しをステートメントとして加える。ターゲットはスクリプトを記述しているメインタイムラインなので、参照は省略した。[ムービープレビュー] してみると、今度はインスタンスがステージ上に表示される図6⁠。なお、インスタンスのxy座標も、ステージ中央に設定した。

var my_mc:EllipticMotion = new EllipticMotion();
addChild(my_mc);
my_mc.x = stage.stageWidth / 2;
my_mc.y = stage.stageHeight / 2;
図6 表示リストに加えられてステージに表示されたインスタンス
図6 表示リストに加えられてステージに表示されたインスタンス

フレームアクションをクラスに移行する

これで必要な知識は学んだので、第17回「3D風に回転するアニメーション」で作成したスクリプト3を、フレームアクションからカスタムクラスの定義に書替えてみよう。フレームアクションに最小限の修正をしてクラス定義に直したのが以下のスクリプト1だ。

第1に、import宣言は、MovieClipクラスだけでなく、Event(flash.events.Event)およびBlurFilter(flash.filters.BlurFilter)クラスについても必要になる。第2に、クラス定義本体に記述するのはvar宣言したプロパティとfunctionで定義するメソッドのみなので、イベントリスナーを設定するステートメントは、コンストラクタメソッド内に置いた。第3に、アクセス制御の属性は、コンストラクタメソッドをpublicとした以外は、すべてprivateで指定した[4]⁠。

スクリプト1 インスタンスを3D風に回転させるEllipticMotionクラスの定義
// ActionScript 3.0クラス定義ファイル: EllipticMotion.as
// 回転するMovieClipシンボルの[クラス]に設定
package {
  import flash.display.MovieClip;
  import flash.events.Event;
  import flash.filters.BlurFilter;
  public class EllipticMotion extends MovieClip {
    private var nDegree:Number = 0;
    private var nRadian:Number = 0;
    private var nSpeed:Number = 5;
    private var nDegreeToRadian:Number = Math.PI / 180;
    private var nCenterX:Number = stage.stageWidth / 2;
    private var nCenterY:Number = stage.stageHeight / 2;
    private var nRadiusX:Number = 100;
    private var nRadiusY:Number = 50;
    private var nCos:Number = Math.cos(nRadian);
    private var nSin:Number = Math.sin(nRadian);
    public function EllipticMotion() {
      addEventListener(Event.ENTER_FRAME, xMoveX);
      addEventListener(Event.ENTER_FRAME, xMoveY);
      addEventListener(Event.ENTER_FRAME, xScale);
      addEventListener(Event.ENTER_FRAME, xBlur);
      addEventListener(Event.ENTER_FRAME, xUpdate);
    }
    private function xMoveX(eventObject:Event):void {
      x = nCenterX + nCos * nRadiusX;
    }
    private function xMoveY(eventObject:Event):void {
      y = nCenterY + nSin * nRadiusY;
    }
    private function xScale(eventObject:Event):void {
      scaleX = scaleY = xGetIndexZ(0.8,1);
      scaleX *= xGetIndexZ();
    }
    private function xBlur(eventObject:Event):void {
      var nBlur:Number=xGetIndexZ(4,0);
      var myBlur:BlurFilter=new BlurFilter(nBlur,nBlur/2);
      filters=[myBlur];
    }
    private function xUpdate(eventObject:Event):void {
      nDegree+=nSpeed;
      nDegree = (nDegree%360+360)%360;
      nRadian=nDegree*nDegreeToRadian;
      nCos=Math.cos(nRadian);
      nSin=Math.sin(nRadian);
    }
    private function xGetIndexZ(nMin:Number=-1, nMax:Number=1):Number {
      var nIndexZ:Number = (nMax-nMin)*(nSin+1)/2+nMin;
      return nIndexZ;
    }
  }
}

上記スクリプト1のクラスEllipticMotionを設定したMovieClipインスタンスは、まずはステージに予め置いて試してみよう(ダウンロードしたサンプルを利用している人は、フレームアクションは削除しておくことを忘れないように⁠⁠。[ムービープレビュー]を見ると、フレームアクションと同じように、インスタンスが3D風に回転する図7⁠。

図7 クラスを設定したMovieClipインスタンスは3D風に回転する
図7 クラスを設定したMovieClipインスタンスは3D風に回転する

それではつぎに、MovieClipインスタンスをステージからは消して、スクリプトで動的に作成してみる。インスタンスをコンストラクタメソッドで生成し、DisplayObjectContainer.addChild()メソッドに渡せばよかった。

var my_mc:EllipticMotion = new EllipticMotion();
addChild(my_mc);

ところが、以下のランタイムエラーが発生してしまう。このエラーは、オブジェクトを参照してプロパティにアクセスしようとしているステートメントで、実際にはオブジェクトが存在しない場合に起こる[5]⁠。しかも、たちの悪いことに、何行目のステートメントが問題なのかを具体的に知らせはてくれない。

TypeError: Error #1009: nullのオブジェクト参照のプロパティまたはメソッドにアクセスすることはできません。 

結論からいうと、原因はクラスEllipticMotionにおけるプロパティのvar宣言で、DisplayObject.stageプロパティにアクセスしている点にある図8⁠。[ヘルプ]の[ActionScript 3.0言語およびコンポーネントリファレンス]でDisplayObject.stageプロパティの項を見ると、つぎのような注意書きがある。

表示オブジェクト(野中注: DisplayObjectインスタンス)が表示リストに追加されていない場合、stageプロパティはnullに設定されます。
図8 表示リストに加わる前はDisplayObject.stageプロパティがnull
図8 表示リストに加わる前はDisplayObject.stageプロパティがnull

前掲のフレームアクションで、EllipticMotionクラスのコンストラクタを呼出してインスタンスを作成しただけでは、まだStageオブジェクトを頂点とするツリー構造の表示リスト前掲図5には加えられていない。その状態では、インスタンスのDisplayObject.stageプロパティに値がなく(null)、Stageインスタンスにアクセスできないのだ。

対処の方法は、いくつか考えられる。今回は、 EllipticMotionクラスに宣言した楕円軌道の中心座標をもつインスタンスプロパティの値は、クラスの外つまりフレームアクションから渡すことにしよう。具体的には、楕円軌道の中心座標とxy半径の値をコンストラクタメソッドの引数とする。さらに、複数のインスタンスを生成して配置したいので、角度の初期値も引数に加えよう。

中心や半径の値は、xとyの値の組になっている。これらをばらばらに指定すると引数の数が増えてしまうし、ひと組として扱うべきものはひとつの値で指定できると便利だ。そこで、xy座標の値をひとつのインスタンスとして扱うPointクラスを利用しよう。Pointクラスのコンストラクタメソッドには、xとyの値を引数に渡す。そして、それぞれの値には、Point.xおよびPoint.yプロパティとしてアクセスすればよい。

new Point(x座標, y座標)

以上の修正をクラスEllipticMotionに加えたのが、以下のスクリプト2だ。第1に、Pointクラスの完全修飾クラス名flash.geom.Pointをimport宣言した。第2に、楕円軌道の中心座標およびxy半径を納めるプロパティ値は、コンストラクタメソッドに渡されるので、var宣言で初期値は与えない。第3に、コンストラクタが受取る角度と中心座標、およびxy半径の引数を、メソッド本体でブロパティ値に設定している。後のふたつはPointインスタンスで渡されるので、xyそれぞれの値はPoint.xおよびPoint.yプロパティとして取出した。なお、この機会にプロパティとメソッドの名前から接頭辞は除いた。

スクリプト2 EllipticMotionクラスのコンストラクタに角度/中心座標/半径を引数として渡す
// ActionScript 3.0クラス定義ファイル: EllipticMotion.as
// 回転するMovieClipシンボルの[クラス]に設定
package {
  import flash.display.MovieClip;
  import flash.events.Event;
  import flash.filters.BlurFilter;
  import flash.geom.Point;
  public class EllipticMotion extends MovieClip {
    private var degree:Number = 0;
    private var radian:Number = 0;
    private var speed:Number = 5;
    private var degreeToRadian:Number = Math.PI / 180;
    private var centerX:Number;   // 初期値なし
    private var centerY:Number;   // 初期値なし
    private var radiusX:Number;   // 初期値なし
    private var radiusY:Number;   // 初期値なし
    private var cos:Number = Math.cos(radian);
    private var sin:Number = Math.sin(radian);
    public function EllipticMotion(nDegree:Number, myCenter:Point, myRadius:Point) {
      degree = nDegree;
      centerX = myCenter.x;
      centerY = myCenter.y;
      radiusX = myRadius.x;
      radiusY = myRadius.y;
      addEventListener(Event.ENTER_FRAME, moveX);
      addEventListener(Event.ENTER_FRAME, moveY);
      addEventListener(Event.ENTER_FRAME, scale);
      addEventListener(Event.ENTER_FRAME, blur);
      addEventListener(Event.ENTER_FRAME, update);
    }
    private function moveX(eventObject:Event):void {
      x = centerX + cos * radiusX;
    }
    private function moveY(eventObject:Event):void {
      y = centerY + sin * radiusY;
    }
    private function scale(eventObject:Event):void {
      scaleX = scaleY = getIndexZ(0.8,1);
      scaleX *= getIndexZ();
    }
    private function blur(eventObject:Event):void {
      var nBlur:Number=getIndexZ(4,0);
      var myBlur:BlurFilter=new BlurFilter(nBlur,nBlur/2);
      filters=[myBlur];
    }
    private function update(eventObject:Event):void {
      degree += speed;
      degree = (degree%360+360)%360;
      radian=degree*degreeToRadian;
      cos=Math.cos(radian);
      sin=Math.sin(radian);
    }
    private function getIndexZ(nMin:Number=-1, nMax:Number=1):Number {
      var nIndexZ:Number = (nMax-nMin)*(sin+1)/2+nMin;
      return nIndexZ;
    }
  }
}

これで、EllipticMotionクラスのコンストラクタに角度と中心座標、およびxy半径を引数で指定して、インスタンスを配置することができる。試しに、4つのインスタンスを生成し、90度間隔で回転させてみよう。フレームアクションは、つぎのようになる。

var myCenter:Point = new Point(stage.stageWidth / 2,stage.stageHeight / 2);
var myRadius:Point = new Point(100,50);
var _mc:EllipticMotion;
_mc = new EllipticMotion(0,myCenter,myRadius);
addChild(_mc);
_mc = new EllipticMotion(90,myCenter,myRadius);
addChild(_mc);
_mc = new EllipticMotion(180,myCenter,myRadius);
addChild(_mc);
_mc = new EllipticMotion(270,myCenter,myRadius);
addChild(_mc);

前述のとおり、楕円軌道の中心座標とxy半径は、 EllipticMotionクラスのコンストラクタにPointインスタンスで渡す。[ムービープレビュー]を確かめると、4つのインスタンスが90度間隔で配置され、楕円軌道を描いてアニメーションする図9)。前記フレームアクションでは、4つのインスタンスの生成と表示リストへの追加が、似たようなステートメントをただ羅列した処理になっている。このような繰返し処理のすっきりした書き方や、EllipticMotionクラスのさらなるリファインについては、次回に譲ろう。

図9 4つのインスタンスが動的に配置されて回転する
図9 4つのインスタンスが動的に配置されて回転する

今回解説した次のサンプルファイルがダウンロードできます。

おすすめ記事

記事・ニュース一覧