サイトトップ

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

Flash OOP for ActionScript 3.0

第2章 ActionScript 2.0から3.0へのガイド
◎2-3 クラス定義とインスタンスの動的な生成

●2-3-1 ActionScript 2.0のクラス定義
ActionScript 2.0から、ECMAScript 4([MEMO]2-1-005「ECMAScript 4 」参照)に準拠したクラス定義ができるようになりました。ActionScript 3.0との違いについて説明する前に、2.0のクラスの作成の仕方を確認しておきましょう。

2-3-1-1 MovieClipインスタンスを楕円軌道で3D風に回転するクラスの定義 ー ActionScript 2.0
ActionScript 2.0では、クラスはつぎのようなかたちで定義します。

// ActionScript 2.0クラス定義ファイル: クラス名.as
class クラス名 {
  // プロパティをvar宣言
  function クラス名([引数]) {   // コンストラクタ
    // 初期化の処理
  }
  // メソッドをfunciton定義
}

まず第1に、クラス定義はclassステートメントではじめ、クラス名を指定します。そして第2に、クラス本体(中括弧内)にはプロパティとメソッドを記述します。プロパティはvarステートメントで宣言し、メソッドはfunctionとして定義します。第3に、クラスと同名の特別なメソッド、コンストラクタが必要です。最後に第4として、クラスはActionScriptファイルとして、「クラス名.as」という名前で保存しなければなりません。

それでは、実際にActionScript 2.0クラス定義のサンプルを作成します。処理内容は、前節2-2-3-2「ひとつのイベントに複数のリスナーを登録できる」で作成したスクリプト2-2-003と同じく、MovieClipインスタンスを3D風に楕円軌道で回転させるものとします。

クラスの構成としては、まずコンストラクタにMovieClipインスタンスその他の値を引数として渡し、クラスのインスタンスを生成します。その後、楕円軌道のアニメーションは、そのためのメソッドを呼出して開始します。具体的には、クラス名はRotationとし、コンストラクタには引数としてMovieClipインスタンスと楕円の中心座標およびx軸y軸それぞれの方向の半径を渡します。楕円軌道のアニメーション開始するのは、メソッドstartRotation()とします。

たとえば、Flashムービー(FLA)ファイルにMovieClipインスタンスmy_mcを配置したうえで、フレームアクションからクラスRotationをつぎのように使用します(図2-3-001)。

// メインタイムライン
// MovieClipインスタンスmy_mcを配置
import flash.geom.Point;
var center:Point = new Point(Stage.width/2, Stage.height/2);
var radius:Point = new Point(Stage.width*0.35, Stage.height*0.2);
var myObject:Rotation = new Rotation(my_mc, center, radius);
myObject.startRotation();

▲図2-3-001■クラスRotationを使ったフレームアクション
Flash_OOP_2-2-001.gif
△コンストラクタにMovieClipインスタンスと楕円の中心座標およびxy軸の半径を引数として渡す。Rotation.startRotation()メソッドで楕円軌道のアニメーションが開始。

コンストラクタに渡す楕円の中心座標とx軸y軸それぞれの半径は、xyの値をセットで扱えるように、Pointインスタンス(centerとradius)で指定することにしました。Pointクラスは幾何学データを扱うflash.geomパッケージに属しますので、importステートメントで読込んでおきます。

上述の仕様に合わせて、ActionScript 2.0クラスRotationは以下のスクリプト2-3-001のように定義しました。ActionScript 2.0クラス定義ファイルは、フレームアクションを記述したFlashムービー(FLA)ファイルと同じ場所にRotation.asという名前で保存します。

▲スクリプト2-3-001■MovieClipインスタンスを楕円軌道で3D風に回転するクラス ー ActionScript 2.0

// ActionScript 2.0クラス定義ファイル: Rotation.as
import mx.utils.Delegate;
import flash.geom.Point;
class Rotation {
  private var target_mc:MovieClip;
  private var center:Point;
  private var radius:Point;
  private var nRadian:Number = 0;
  private var nCos:Number = 1;   // Math.cos(nRadian);
  private var nSin:Number = 0;   // Math.sin(nRadian);
  private var nIncrements:Number = 0.1;
  public function Rotation(my_mc:MovieClip, myCenter:Point, myRadius:Point) {
    target_mc = my_mc;
    center = myCenter;
    radius = myRadius;
  }
  public function startRotation():Void {
    target_mc.onEnterFrame = Delegate.create(this, xRotate);
  }
  public function stopRotation():Void {
    delete target_mc.onEnterFrame;
  }
  private function xRotate() {
    xMoveX();
    xMoveY();
    xScale();
    xUpdate();
  }
  private function xMoveX():Void {
    target_mc._x = center.x+nCos*radius.x;
  }
  private function xMoveY():Void {
    target_mc._y = center.y+nSin*radius.y;
  }
  private function xScale():Void {
    target_mc._xscale = nSin*100;
  }
  private function xUpdate():Void {
    nRadian += nIncrements;
    nCos = Math.cos(nRadian);
    nSin = Math.sin(nRadian);
  }
}

△毎フレーム処理したい4つのメソッドは、MovieClip.onEnterFrameイベントハンドラメソッドに設定したひとつのメソッドから呼出している。

2-3-1-2 ActionScript 2.0を3.0と比較するためのポイント
ActionScript 3.0のクラス定義と比べるときのポイントとなる点を、簡単に説明しておきます。

第1に、インスタンスプロパティを宣言時に初期化する際、関数やメソッドの戻り値を代入することはできません(図2-3-002)。そのため、前記スクリプト2-3-001で、プロパティnCosとnSinの初期値にはMath.cos()Math.sin()メソッドが使えず、cos値1およびsin値0をそれぞれ直接数値で設定しています。

▲図2-3-002■インスタンスプロパティの宣言時にメソッドを呼出して初期値を与えることはできない
Flash_OOP_2-2-002.gif
△インスタンスプロパティの宣言時にメソッドの戻り値で初期化しようとすると、「コンパイル時の定数式」しか与えられない旨の[コンパイルエラー]が発生する。

第2に、イベントハンドラメソッドMovieClip.onEnterFrameには、前節2-2-1「ActionScript 2.0/1.0のイベントハンドラメソッド」で述べたとおり、コールバック関数はひとつしか設定できません。しかし、呼出したいメソッドは全部で4つ(xMoveX()、xMoveY()、xScale()、およびxUpdate())あります。そこで、イベントハンドラメソッドに設定するためのメソッドxRotate()を別に定義して、その関数ブロック{}内から、4つのメソッドを呼出しています。

第3に、イベントハンドラメソッドMovieClip.onEnterFrameにコールバック関数としてメソッドxRotateを直接設定せず、Delegate.create()メソッドで関数のスコープをクラスインスタンスに委譲していることです(Delegateクラスと委譲については、筆者サイトFumioNonaka.com「イベントの委譲 (Delegateクラス)」<http://www.fumiononaka.com/TechNotes/Flash/FN0408001.html>参照)。

ActionScript 2.0では、関数は自らの設定されたオブジェクトをスコープとして実行されます。つまり、前記スクリプト2-3-001でDelegate.create()メソッドを使わず、MovieClip.onEnterFrameイベントハンドラメソッドに直接xRotateメソッドを設定すれば、メソッド内のthis参照はイベントハンドラメソッドのターゲットであるMovieClipインスタンスtarget_mcになります。

そして、ActionScript 2.0のクラス定義では、参照のないプロパティやメソッドにはthis参照が自動的に補われます。すると、xRotate()メソッド内はMovieClipインスタンスtarget_mcのスコープとなり、Rotationクラスに定義されたメソッドにはアクセスできなくなってしまいます(図2-3-003)。ですから、Delegate.create()メソッドを使って、xRotate()メソッドのスコープをRotationインスタンスに委譲する必要があるのです。

▲図2-3-003■イベントハンドラメソッドのターゲットがコールバック関数のスコープになる
Flash_OOP_2-2-003.gif
△コールバック関数に設定したメソッド内から、クラスに定義されたメソッドにアクセスすることができない。

第4に、プロパティやメソッドのアクセスを制限する属性として、ActionScript 2.0はpublicprivateのふたつのステートメント(Javaでは「アクセス修飾子」と呼ばれます)を備えています。前記スクリプト2-3-001では、FlashムービーからアクセスするコンストラクタとRotation.startRotation()メソッドのふたつのみをpublic属性で指定し、他のプロパティはすべてprivateとしました。それぞれの属性のメンバー(プロパティとメソッド)がアクセスを許す範囲は、下表2-3-001のとおりです。なお、メンバーにアクセス制御の属性を指定していないときには、デフォルトとしてpublic属性が適用されます。

▲表2-3-001■ActionScript 2.0のアクセス制御の属性
アクセス制御の属性
同一クラス内
サブクラス
任意のクラス
private ×
public(デフォルト)

ActionScript 2.0クラスRotationの定義(スクリプト2-3-001)されたActionScript(AS)ファイルと同じ場所にFlashムービー(FLA)ファイル保存し、タイムラインにMovieClipインスタンスを配置したうえで、前述(図2-3-001)のようなフレームアクションを記述すれば、インスタンスが指定した中心座標とxy軸それぞれの半径で楕円軌道を描いて3D風に回転します。なお、Rotation.stopRotation()メソッドは、Rotation.startRotation()と対のメソッドトして定義しました。前述のフレームアクションのサンプルでは、とくにこのメソッドは使っていません。


●2-3-2 ActionScript 3.0のクラス定義
ActionScript 3.0も、2.0と同じく、ECMAScript 4に準拠します。ですから、おおもととなる文法については、基本的にそれほど大幅な違いはありません。ただし、前述2-1-2-3「言語体系」で触れたとおり、細かな仕様の変更は確認しておく必要があります。

2-3-2-1 MovieClipインスタンスを楕円軌道で3D風に回転するクラスの定義 ー ActionScript 3.0
まず、クラス定義の基本スタイルについては、package定義キーワードの加わった点が異なるくらいです。

// ActionScript 3.0クラス定義ファイル: クラス名.as
package [パッケージ名]{
  class クラス名 {
    // プロパティをvar宣言
    function クラス名([引数]) { // コンストラクタ
      // 初期化の処理
    }
    // メソッドをfunciton定義
  }
}

package定義キーワードにより指定されるパッケージは、クラスをグループ分けする機能があります。パッケージ名はオプションですので、指定しないこともできます。パッケージ名を指定しない場合、クラスの定義されたActionScript(AS)ファイルは、通常クラスを用いるFlashムービー(FLA)ファイルと同じ場所に保存します。

[MEMO]2-3-001 ActionScript 2.0のパッケージ
ActionScript 2.0にも、パッケージの仕組みは備わっていました。ただし、package定義キーワードはありませんので、class定義キーワードでクラス名としてパッケージの加わった完全修飾クラス名を指定しました。詳しくは、ヘルプの[ActionScript 2.0 の学習]→[クラス]→[例: カスタムクラスの記述]→[クラスファイルの作成とパッケージ化]をご参照ください。

実際にActionScript 3.0のクラスを定義すると、言語体系の整備にともなって細かな仕様が変わった点もありますので、個別の注意は必要になります。まずは、もっともシンプルな空のクラスを定義してみます。つぎのクラスMyClassに定義したのは、コンストラクタメソッドのみで、プロパティやメソッドはひとつもありません。

// ActionScript 3.0クラス定義ファイル: MyClass.as
package {
  class MyClass {
    function MyClass() {   // コンストラクタ
    }
  }
}

[MEMO]2-3-002 コンストラクタの記述は省略できる
クラスには、コンストラクタが必要です。しかし、カスタムクラスを定義する際には、空のコンストラクタメソッドであれば、記述しなくても差支えはありません。なぜなら、「クラスでコンストラクタメソッドを定義しなかった場合、コンパイラが自動的に空のコンストラクタを作成します」(ヘルプ[ActionScript 3.0のプログラミング]→[ActionScriptのオブジェクト指向プログラミング]→[クラス]→[メソッド]「コンストラクタメソッド」参照)。なお、ActionScript 2.0でも、この点は同じです。

このActionScript 3.0クラスMyClassのASファイルと同じ場所に保存したFlashムービー(FLA)に以下のようなフレームアクションを記述して、MyClassのインスタンスを作成すると、[ムービープレビュー]で[コンパイルエラー]がふたつ表示されます(図2-3-002)。いずれも、クラスMyClassが正しく認識されていないことを示唆しているようです。

// メインタイムライン
var myObject:MyClass = new MyClass();

▲図2-3-004■空のクラスのインスタンスを作成して表示された[コンパイルエラー]
Flash_OOP_2-3-004.gif
△ふたつのエラーとも、クラスMyClassが認識されていないことを示す。

ActionScript 2.0との違いのひとつとして、3.0ではクラスについてもアクセス制限の属性が備わりました。そして、Flashムービーのタイムラインからアクセスするには、クラスをpublic属性で指定しなければなりません。クラスに対して指定できる他の属性については、メンバーのアクセス制限の属性と併せて後述します。

それでは、前述のActionScript 2.0クラス定義(スクリプト2-3-001)と同じように、MovieClipインスタンスが3D風に楕円軌道で回転する前節のスクリプト2-2-003を、ActionScript 3.0クラスRotationとして以下のように定義してみます(スクリプト2-3-002)。

クラスを定義したActionScript(AS)ファイルは、クラスを用いるFlashムービー(FLA)と同じ場所に保存します。Rotationクラスを使うFlashムービーのフレームアクションは、ActionScript 2.0クラス(スクリプト2-3-001)の場合と基本的に同じ処理内容です。

// メインタイムライン
// MovieClipインスタンスmy_mcを配置
var center:Point = new Point(stage.stageWidth/2, stage.stageHeight/2);
var radius:Point = new Point(stage.stageWidth*0.35, stage.stageHeight*0.2);
var myObject:Rotation = new Rotation(my_mc, center, radius);
myObject.startRotation();
stage.addEventListener(MouseEvent.MOUSE_DOWN, xStop);
function xStop(eventObject:MouseEvent):void {
  myObject.stopRotation();
}
▲スクリプト2-3-002■MovieClipインスタンスを楕円軌道で3D風に回転するクラス ー ActionScript 3.0

// ActionScript 3.0クラス定義ファイル: Rotation.as
package {
  import flash.display.MovieClip;
  import flash.events.Event;
  import flash.geom.Point;
  public class Rotation {
    private var target_mc:MovieClip;
    private var center:Point;
    private var radius:Point;
    private var nRadian:Number = 0;
    private var nCos:Number = Math.cos(nRadian);
    private var nSin:Number = Math.sin(nRadian);
    private var nIncrements:Number = 0.1;
    public function Rotation(my_mc:MovieClip, myCenter:Point, myRadius:Point) {
      target_mc = my_mc;
      center = myCenter;
      radius = myRadius;
    }
    public function startRotation():void {
      target_mc.addEventListener(Event.ENTER_FRAME, xMoveX);
      target_mc.addEventListener(Event.ENTER_FRAME, xMoveY);
      target_mc.addEventListener(Event.ENTER_FRAME, xScale);
      target_mc.addEventListener(Event.ENTER_FRAME, xUpdate);
    }
    public function stopRotation():void {
      trace(this);
      target_mc.removeEventListener(Event.ENTER_FRAME, xMoveX);
      target_mc.removeEventListener(Event.ENTER_FRAME, xMoveY);
      target_mc.removeEventListener(Event.ENTER_FRAME, xScale);
      target_mc.removeEventListener(Event.ENTER_FRAME, xUpdate);
    }
    private function xMoveX(eventObject:Event):void {
      target_mc.x = center.x+nCos*radius.x;
    }
    private function xMoveY(eventObject:Event):void {
      target_mc.y = center.y+nSin*radius.y;
    }
    private function xScale(eventObject:Event):void {
      target_mc.scaleX = nSin;
    }
    private function xUpdate(eventObject:Event):void {
      nRadian += nIncrements;
      nCos = Math.cos(nRadian);
      nSin = Math.sin(nRadian);
    }
  }
}

△クラス定義にpackageキーワードが加わり、タイムラインからアクセスするクラスにはpublic属性の指定が必要。

なお、クラス定義ファイルは、ActionScript 3.0でも2.0と同じく、コンパイル([パブリッシュ])時にActionScript(AS)ファイルから読込まれてSWFに書出されます。したがって、クラス定義ファイルだけを書替えても、コンパイルしないかぎりはSWFコンテンツの動作に反映されません。また、ActionScript(AS)ファイルは、サーバーにアップロードする必要はありません。

2-3-2-2 ActionScript 3.0と2.0の違い
ActionScript 3.0と2.0との違いを示す第1の点として、クラスRotation冒頭のimportステートメントがあります。Pointクラスだけでなく、MovieClipやEventクラスもそれぞれflash.displayおよびflash.eventsというパッケージに属しているため、インポートが必要になります。

ところが、Flashムービー(FLA)ファイルのフレームアクション(前掲スクリプト2-2-003)には、importステートメントが1行も書かれていません。Flashムービーのフレームアクションについては、SWFを書出すときにFlashアプリケーションがおもなパッケージを自動的にインポートする仕組みになっているからです。ただし、クラス定義ではimportステートメントは、必ず記述しなければなりません。

[MEMO]2-3-003 タイムラインに自動的にインポートされるパッケージ
Flash CS3のタイムラインに自動的にインポートされるクラスは、アプリケーションフォルダ内のXMLファイルConfigration/ActionScript 3.0/implicitImports.xmlに記述されています(図2-3-005)。

▲図2-3-005■implicitImports.xmlが自動的にインポートするパッケージを指定
Flash_OOP_2-3-005.gif
△Flash CS3アプリケーションフォルダ内のConfigration/ActionScript 3.0/implicitImports.xmlに記述されている。


[CAUTION]2-2-001 ActionScript 3.0ではクラスのインポートがつねに必要
ActionScript 2.0では、パッケージに属するクラスを完全修飾クラス名で指定すれば、importステートメントは記述しないことも可能でした。しかし、ActionScript 3.0では、パッケージに属するクラスは、必ずインポートしなければなりません。

第2に、インスタンスプロパティの宣言時に初期値として、関数やメソッドの戻り値を代入することが可能です。ですから、前述のActionScript 2.0のクラス定義(スクリプト2-3-001)とは異なり、プロパティnCosおよびnSinのvar宣言で、それぞれMath.cos()およびMath.sin()メソッドの戻り値を与えて初期化しています。

第3に、メンバーに対するアクセス制限の属性について、ActionScript 3.0では2.0と仕様が変わりました。まず、属性にprotectedinternalが新たに加わりました。そして、private属性のアクセスを許す範囲がそのクラスのみになり、サブクラスから参照することはできなくなりました。サブクラスからアクセスできるのはprotectedで、この属性がActionScript 2.0のprivateに相当します(前掲表2-3-001「ActionScript 2.0のアクセス制御の属性」参照)。また、internalは、同じパッケージ内からアクセスができる属性です(下表2-3-002)。

つぎに前述のとおり、クラスに対して属性の指定ができるようになりました。ただし、指定できるのはpublicinternalになります。メンバーやクラスにアクセス制限の属性を指定しない場合には、デフォルトとしてinternalが適用されます。ただし、コンストラクタはpublic属性以外は指定できません。したがって、当然デフォルトもpublicです。

▲表2-3-002■ActionScript 23.0のアクセス制御の属性(*修正: 08/02/16)
アクセス制御の属性
同一クラス内
サブクラス
同一パッケージ内
任意のクラス
private × × ×
protected × ×
internal × ×
public

[MEMO]2-3-004 コンストラクタへのprivate属性の指定
ActionScript 2.0では、コンストラクタにprivate属性を指定することができました。けれども、ActionScript 3.0では、コンストラクタにはつねにpublic属性が適用されます。そのため、メンバーへのアクセスは許しつつも、コンストラクタを呼出せないクラスが、アクセス制限の属性を指定するだけでは構成できなくなりました(後述2-5-1-4 「プライベートなコンストラクタ」参照)。

第4に、MovieClipインスタンスtarget_mcにイベントリスナーを登録するとき、リスナー関数に対してActionScript 2.0のDelegate.create()メソッドのようなスコープを委譲する操作が行われていないことに注目すべきです。

ActionScript 3.0で定義された関数(メソッド)は、定義先インスタンスのスコープを保持します。前記スクリプト2-3-002におけるDisplayObject.enterFrameイベントの4つのリスナー関数(xMoveX()、xMoveY()、xScale()、およびxUpdate())はクラスRotationに定義されていますので、たとえMovieClipインスタンスtarget_mcのリスナー関数として登録されても、Rotationインスタンスのスコープで実行されるのです。したがって、スコープの委譲という操作が不要になります。

簡単なサンプルスクリプトで確認してみましょう。タイムラインにMovieClipインスタンスmy_mcを置いて、つぎのフレームアクションを記述します(関数test()の戻り値の型指定は、ActionScript 2.0では頭文字が大文字のVoidに変更します)。

var name_str:String = "main";
my_mc.name_str = "my_mc";
function test():void {   // :Void {   // ActionScript 2.0の場合
  trace(this.name_str);
}
my_mc.test = test;
test();
my_mc.test();

[ムービープレビュー]を確かめると、ActionScript 2.0では関数test()をタイムラインからそのまま呼出した場合と、MovieClipインスタンスmy_mcの関数testとして設定した場合とでは[出力]結果が異なります。関数をインスタンスmy_mcに設定すると、その関数ブロック{}内はmy_mcのスコープとなるため、thisがスクリプトを記述したタイムラインではなくインスタンスを参照することになるからです。

// ActionScript 2.0の[出力]:
main
my_mc

ところが、ActionScript 3.0では、関数は自身の定義されたスコープを保持します。したがって、関数をMovieClipインスタンスmy_mcに設定しても、関数の定義されたタイムラインのスコープで実行されるため、関数ブロック{}内のthisはつねにそのタイムラインを参照します。そのため、上記サンプルスクリプトのふたつの関数呼出しの[出力]結果は同じになります。

// ActionScript 3.0の[出力]:
main
main

なお、関数およびメソッドとそのスコープについては、[ヘルプ]の[ActionScript 3.0のプログラミング]→[ActionScript言語とシンタックス]→[関数]→[関数のスコープ]および[ActionScript3.0のプログラミング]→[ActionScriptのオブジェクト指向プログラミング]→[クラス]→[メソッド]をご参照ください。

[*筆者用参考] akihiro kamijo「thisとクロージャ」。

[MEMO]2-3-005 名前のない関数とスコープ
ActionScript 3.0でも、名前のない関数はスコープの変更が可能です。たとえば、前記サンプルスクリプトの関数test()を以下のように書替えてみます。すると、名前のない関数はMovieClipインスタンスに設定するとスコープが変わり、関数ブロック{}内ではthisが設定先インスタンスを参照することになります。

var name_str:String = "main";
my_mc.name_str = "my_mc";
// function test():void {
var test:Function = function ():void {
  trace(this.name_str);
};
my_mc.test = test;
test();   //[出力]: main
my_mc.test();   //[出力]: my_mc

なお、タイムラインに記述した関数やクラスに定義したメソッドはスコープを保持しますので、Function.apply()Function.call()メソッドでthis参照を変更することはできません。これらのメソッドが効果をもつのは、やはり名前のない関数を参照する場合です。


●2-3-3 インスタンスの動的な配置
ステージに表示されるビジュアルエレメントとなるインスタンスを動的に配置する方法は、ActionScript 3.0と2.0/1.0では考え方が大きく変わりました。ActionScript 2.0/1.0ではインスタンスを生成するために特別なメソッドを用い、表示の重ね順を決める深度の指定が必要でした。ActionScript 3.0は、インスタンスをすべてコンストラクタで生成します。そして、ステージへの表示や重ね順は、表示リストで管理します。

2-3-3-1 ActionScript 2.0/1.0は特別なメソッドを使う
ActionScript 2.0/1.0で、たとえばMovieClipインスタンスをスクリプトでダイナミックに配置する場合、コンストラクタを呼出すnew MovieClip()というステートメントではインスタンスが生成できません。MovieClip.attachMovie()MovieClip.createEmptyMovieClip()MovieClip.duplicateMovieClip()といった、クラスに用意された特別なメソッドを呼出す必要があります。

ActionScript 2.0のサンプルとして、[ライブラリ]のビットマップをステージに配置してみます。まず、[ライブラリ]のビットマップには、予めスクリプトで指定するための「識別子」を設定しておきます(図2-3-006)。

▲図2-3-006■[リンケージプロパティ]ダイアログで[識別子]を設定
Flash_OOP_2-3-006.gif
△[ライブラリ]のオプションメニューから[リンケージ]を選択。[リンケージプロパティ]ダイアログの[ActionScriptに書き出し]をチェックしてから、[識別子]を入力する。

ビットマップは直接ステージに表示できませんので、空のMovieClipインスタンスを配置したうえで、[ライブラリ]から読込んだ画像を設定するという手順になります。メインタイムラインに記述するフレームアクションは、つぎのスクリプト2-3-003のようになります。

▲スクリプト2-3-003■[ライブラリ]からビットマップをロードして空のMovieClipインスタンスに設定

// タイムラインメイン
// [ライブラリ]に識別子"Pen"のビットマップを格納
import flash.display.BitmapData;
var myBitmapDataa:BitmapData = BitmapData.loadBitmap("Pen");
var _mc:MovieClip = createEmptyMovieClip("my_mc", getNextHighestDepth());
_mc.attachBitmap(myBitmapDataa, _mc.getNextHighestDepth());

BitmapData.loadBitmap()メソッドでロードした[ライブラリ]のビットマップを、MovieClip.createEmptyMovieClip()メソッドで配置した空のインスタンスに設定する。

[ライブラリ]のビットマップは、BitmapData.loadBitmap()メソッドの引数に識別子を渡してロードします。画像はBitmapDataインスタンスとしてメモリにロードされるだけで、ステージには表示されません。ステージにMovieClipインスタンスを作成したうえで、BitmapDataインスタンスをそのMovieClipに設定する必要があります。

空のMovieClipインスタンスは、MovieClip.createEmptyMovieClip()メソッドでタイムラインに配置します。そしてそのインスタンスをターゲットとしてMovieClip.attachBitmap()メソッドを呼出し、引数にBitmapDataインスタンスを与えます。これで、[ライブラリ]からロードしたビットマップが、ステージに配置したMovieClipインスタンスに設定されて表示されます(図2-3-007)。

▲図2-3-007[ライブラリ]のビットマップをMovieClipインスタンスに設定して配置
Flash_OOP_2-3-007.gif
BitmapData.loadBitmap()メソッドでロードしたビットマップを、空のMovieClipインスタンスにMovieClip.attachBitmap()メソッドで設定。

BitmapDataインスタンスはBitmapData.loadBitmap()メソッドで[ライブラリ]からビットマップをロードして作成し、MovieClipインスタンスはMovieClip.createEmptyMovieClip()メソッドで生成してタイムラインに配置しました。いずれもコンストラクタの呼出しではなく、クラスに用意された特別のメソッドを使っています。また、MovieClip.createEmptyMovieClip()およびMovieClip.attachBitmap()メソッドは、表示の重ね順を決める深度を第2引数として指定しています。

2-3-3-2 ActionScript 3.0はコンストラクタと表示リストでインスタンスを操作
つぎはActionScript 3.0で、[ライブラリ]のビットマップをステージに配置してみましょう。[ライブラリ]のビットマップには、やはり予め設定が必要です。ただし、設定するのは[識別子]ではなく[クラス]です(図2-3-008)。

▲図2-3-008[リンケージプロパティ]ダイアログで[クラス]を設定
Flash_OOP_2-3-009.gif
△[ライブラリ]のオプションメニューから[リンケージ]を選択。[リンケージプロパティ]ダイアログの[ActionScriptに書き出し]をチェックしてから、[クラス]を入力する。

設定するクラスは、定義されている必要はありません。クラス定義が見つからなければ、Flashが自動的にその名前で空のクラスを生成してくれます(図2-3-009)。この場合、[クラス]はスクリプトで[ライブラリ]内のアイテムを指定をするためのもので、ActionScript 2.0における前述の[識別子]と同じ役割を果たします。

[リンケージプロパティ]ダイアログの[基本クラス]は、[クラス]が継承するスーパークラスを示します。デフォルトでBitmapDataクラスが指定されていますので、[クラス]の中身は空でも実質的にはBitmapDataクラスとして扱えます。

▲図2-3-009 クラスの自動生成を警告するダイアログ
Flash_OOP_2-3-010.gif
△クラス定義が見つからないと、Flashが自動的にその名前で空のクラスを生成する。

[ライブラリ]のビットマップからインスタンスを生成し、それをコンテナ(容れ物)となるインスタンスに設定したうえでステージに配置するという処理の流れは、ActionScript 3.0でも2.0と基本的に同じです。けれども、実際のステートメントはもっと端的かつシンプルです。

▲スクリプト2-3-004■[ライブラリ]のビットマップから作成したインスタンスをBitmapインスタンスに設定して配置

// タイムラインメイン
// [ライブラリ]にクラスPenが設定されたビットマップを格納
var myBitmapDataa:BitmapData = new Pen(0, 0);
var myBitmap:Bitmap = new Bitmap(myBitmapDataa);
addChild(myBitmap);

△ビットマップに設定したクラスから作成したインスタンスを、Bitmapクラスのコンストラクタに引数として渡し、ステージに配置。

第1ステートメントは、[リンケージプロパティ]ダイアログで設定した[クラス]のコンストラクタを呼出して、インスタンスを生成します。前述のとおり、実質的にはスーパークラスであるBitmapDataクラスですので、データ型はBitmapDataで指定しています。また、コンストラクタの引数も、BitmapDataの必須の引数である幅と高さを渡す必要があります。この場合は、ともに0で構いません。

第2ステートメントは、Bitmapインスタンスを生成します。ステージに表示するためには、インスタンスはDisplayObjectのサブクラスでなければなりません。BitmapDataはDisplayObjectを継承しませんので、DisplayObjectのサブクラスであるBitmapのコンストラクタに引数として渡します。Bitmapインスタンスはステージに表示できる資格はあるものの、この段階ではメモリに保持されているだけで、表示はされません。

[MEMO]2-3-006 BitmapDataとBitmapクラスの継承
BitmapDataとBitmapクラスの継承は、つぎのとおりです。

BitmapData→Object
Bitmap→DisplayObject→EventDispatcher→Object

第3ステートメントのDisplayObjectContainer.addChild()メソッドで、引数に渡したBitmapインスタンスがターゲットとなるタイムラインの子として登録され、ステージに表示されます。ステージ上に表示されるインスタンスは、Stageインスタンスを頂点とする階層構造をもちます。それぞれのインスタンスは、自らの子として下層に表示するインスタンスを、「表示リスト」という仕組みで管理します。

DisplayObjectContainerはDisplayObjectクラスを継承し、子オブジェクトをもつことのできる、つまり表示リストを備えたクラスです。たとえば、MovieClipもDisplayObjectContainerのサブクラスです。表示リストに登録された子オブジェクトは、配列に似た0から始まる整数インデックスにより管理されます。後から加えられたインスタンスほどインデックスの整数は大きくなり、前面に表示されます。

DisplayObjectContainerクラスには、表示リストを操作する下表2-3-003のようなメソッドがあります(掲載しているのはメソッドの一部です)。ActionScript 2.0の深度のような値を使う必要はなく、表示リスト内の位置(インデックス)を変えることにより、重ね順が変更できます。さらに、インスタンスを表示リストから削除して別の親の表示リストに加えれば、2.0ではできなかった、親を変更することも可能です。

▲表2-3-003■DisplayObjectContainerクラスのおもなメソッド
メソッド
説明
addChild(child:DisplayObject):DisplayObject 表示リストにDisplayObjectインスタンスを追加します。
addChildAt(child:DisplayObject, index:int):DisplayObject 表示リストの指定インデックスにDisplayObjectインスタンスを追加します。
getChildAt(index:int):DisplayObject 表示リストの指定インデックスに格納されている子DisplayObjectインスタンスを返します。
getChildIndex(child:DisplayObject):int 指定された子DisplayObjectインスタンスの表示リストにおけるインデックスを返します。
removeChild(child:DisplayObject):DisplayObject 表示リストから指定のDisplayObjectインスタンスを削除します。
removeChildAt(index:int):DisplayObject 表示リストの指定インデックスに格納されている子DisplayObjectを削除します。
setChildIndex(child:DisplayObject, index:int):void 表示リストに格納されている子DisplayObjectのインデックスを指定された値に変更します。

必要なインスタンスをコンストラクタで作成し、表示リストに加えてステージに表示するという処理の流れは、とてもわかりやすくシンプルです。表示するインスタンスがMovieClipであろうと、TextFieldであろうと、あるいは外部からロードするコンテンツであろうと、基本的な処理は変わりません。スクリプト2-3-004を[ムービープレビュー]で確かめると、ビットマップがステージに表示されます(図2-3-010)。

▲図2-3-010[ライブラリ]のビットマップをMovieClipインスタンスに設定して配置
Flash_OOP_2-3-008.gif
BitmapData.loadBitmap()メソッドでロードしたビットマップを、空のMovieClipインスタンスにMovieClip.attachBitmap()メソッドで設定。

[CAUTION]2-2-002 見えないインスタンスに注意
ActionScript 3.0では、インスタンスを作成しても、表示リストに加えなければ、ステージに表示されません。つまり、ステージ上に現れなくても、メモリにインスタンスが存在するということにご注意ください。

インスタンスを消去してメモリを解放するには、表示リストから削除するだけでは足りません。インスタンスが保持されている変数を(delete演算子により)削除するか、または変数にnull値を代入して、インスタンスへの参照を破棄する必要があります。


●2-3-4 MovieClipシンボルに設定するクラス
本節のはじめに作成した、3D風に回転するクラス(スクリプト2-3-001および2-3-002)を、MovieClipシンボルに設定するように修正してみます。ActionScript 3.0も2.0も、クラスはMovieClipを継承する定義になります。インスタンスは予めタイムラインには置かず、[ライブラリ]からダイナミックに作成することにします。

2-3-4-1 MovieClipシンボルに設定するActionScript 2.0クラス
まず、ActionScript 2.0でクラスを定義します。クラス名はRotationとし、[ライブラリ]のMovieClipシンボルには[リンケージプロパティ]ダイアログで[クラス]にその名前を入力します(図2-3-011)。MovieClip.attachMovie()メソッドを使ってインスタンスをダイナミックに作成しますので、併せて任意の[識別子]も入力します(空欄のままでは[リンケージプロパティ]ダイアログを確定して閉じられません)。

▲図2-3-011[リンケージプロパティ]ダイアログで[クラス]と[識別子]を設定
Flash_OOP_2-3-011.gif
△[クラス]にクラス名Rotationを設定。インスタンスを動的に配置するために[識別子]も入力する。

Rotationクラスは、MovieClipシンボルに設定しますので、MovieClipクラスを継承します。また、座標値などは自身のプロパティになりますので、ターゲットはthisにするか、省略します。さらに、若干の処理を加えたのが、以下のスクリプト2-3-005です。タイムラインからは、Rotationクラスをつぎのように操作します。

// メインタイムライン
// [ライブラリ]に識別子"Pen"が設定されたMovieClipシンボルを格納
var myObject:Rotation = Rotation(attachMovie("Pen", "my_mc", getNextHighestDepth()));
myObject.startRotation();
▲スクリプト2-3-005■MovieClipシンボルに設定する3D風に回転するクラス ー ActionScript 2.0

// ActionScript 2.0クラス定義ファイル: Rotation.as
import flash.geom.Point;
class Rotation extends MovieClip {
  private var center:Point;
  private var radius:Point;
  private var nRadian:Number = 0;
  private var nCos:Number = 1;   // Math.cos(nRadian);
  private var nSin:Number = 0;   // Math.sin(nRadian);
  private var nIncrements:Number = 0.1;
  public function Rotation() {
    center = new Point(Stage.width/2, Stage.height/2);
    radius = new Point(Stage.width*0.35, Stage.height*0.2);
    setPosition();
  }
  private function setPosition():Void {
    xMoveX();
    xMoveY();
    xScale();
  }
  public function startRotation(myCenter:Point, myRadius:Point):Void {
    if (myCenter instanceof Point) {
      center = myCenter;
    }
    if (myRadius instanceof Point) {
      radius = myRadius;
    }
    onEnterFrame = xRotate;
  }
  public function stopRotation():Void {
    delete onEnterFrame;
  }
  public function xRotate() {
    xMoveX();
    xMoveY();
    xScale();
    xUpdate();
  }
  private function xMoveX():Void {
    _x = center.x+nCos*radius.x;
  }
  private function xMoveY():Void {
    _y = center.y+nSin*radius.y;
  }
  private function xScale():Void {
    _xscale = nSin*100;
  }
  private function xUpdate():Void {
    nRadian += nIncrements;
    nCos = Math.cos(nRadian);
    nSin = Math.sin(nRadian);
  }
}

△MovieClipクラスを継承し、プロパティは自らの値として処理。

ActionScript 2.0では、コンストラクタを呼出してMovieClipおよびそのサブクラスのインスタンスを作成することはできません。しかし、シンボルのインスタンスをMovieClip.attachMovie()メソッドで生成すると、設定したクラスのコンストラクタが呼出されます。コンストラクタメソッドでは、ステージサイズを基準に楕円軌道の中心座標とx軸y軸各方向の半径をPointインスタンスで設定しています。

また、インスタンスの初期位置座標を設定するメソッドsetPosition()を定義して、コンストラクタで呼出しています。setPosition()メソッドからは、座標を設定するxMoveX()とxMoveY()、および水平スケールを設定するxScale()メソッドが呼出されます。

アニメーションを開始するstartRotation()メソッドは、引数として中心座標とx軸y軸各方向の半径をPointインスタンスで受取ります。つまり、これらの値の再設定が可能です。しかし、前述したサンプルのフレームアクションでは、startRotation()を引数なしで呼出しています。これらふたつの引数はオプションとして、省略時にはコンストラクタ呼出し時に設定された値が用いられます。

したがって、startRotation()メソッド内では、引数がPoint(あるいはそのサブクラスの)インスタンスかどうかをinstanceof演算子により判定し、引数のない場合の未定義値undefinedや、Pointインスタンス以外の値であれば、再設定の処理をスキップしています。

stopRotation()メソッド以降、xRotate()、xMoveX()、xMoveY()、xScale()、xUpdate()などのメソッドは、設定するプロパティや呼出すメソッドのターゲットをインスタンス自身に変えたほかは、前のサンプルスクリプト2-3-001と変わりありません。

2-3-4-2 MovieClipシンボルに設定するActionScript 3.0クラス
ActionScript 3.0のクラスも名前をRotationとし、[ライブラリ]のMovieClipシンボルに、2.0の場合と同じように、[リンケージプロパティ]ダイアログで[クラス]に名前を入力します。ActionScript 3.0では、クラスが[ライブラリ]のMovieClipシンボルを指定する識別子の役割も果たすので、[リンケージプロパティ]ダイアログに[識別子]は入力しません(入力ができません)。

▲図2-3-012[リンケージプロパティ]ダイアログで[クラス]を設定
Flash_OOP_2-3-012.gif
△[クラス]にクラス名Rotationを設定。クラスがMovieClipシンボルを指定する識別子の役割も果たすので、[識別子]の入力は不要。

ActionScript 3.0のRotationクラスは、2.0のスクリプト2-3-005と同じく、MovieClipクラスを継承し、プロパティやメソッドの参照先を自身に(thisもしくは省略)します。また、クラスにはpublic属性を指定する必要があります。そして、ActionScript 2.0のスクリプト2-3-005と同じように、初期位置の座標を設定するメソッドsetPosition()を新たに加え、startRotation()は省略可能なPointインスタンスの引数ふたつを取ることとしました(スクリプト2-3-006)。

Flashムービー(FLA)ファイルのフレームアクションからは、つぎのようにRotationクラスを操作します。

// メインタイムライン
// [ライブラリ]にクラスRotationが設定されたMovieClipシンボルを格納
var myObject:Rotation = new Rotation(this);
myObject.startRotation();
▲スクリプト2-3-006■MovieClipシンボルに設定する3D風に回転するクラス ー ActionScript 3.0

// ActionScript 3.0クラス定義ファイル: Rotation.as
package {
  import flash.display.MovieClip;
  import flash.events.Event;
  import flash.geom.Point;
  public class Rotation extends MovieClip {
    private var center:Point;
    private var radius:Point;
    private var nRadian:Number = 0;
    private var nCos:Number = Math.cos(nRadian);
    private var nSin:Number = Math.sin(nRadian);
    private var nIncrements:Number = 0.1;
    public function Rotation(my_mc:MovieClip) {
      trace(stage, root);   // 確認用
      my_mc.addChild(this);
      center = new Point(stage.stageWidth/2, stage.stageHeight/2);
      radius = new Point(stage.stageWidth*0.35, stage.stageHeight*0.2);
      setPosition();
    }
    private function setPosition():void {
      xMoveX();
      xMoveY();
      xScale();
    }
    public function startRotation(myCenter:Point=null, myRadius:Point=null):void {
      if (myCenter is Point) {
        center = myCenter;
      }
      if (myRadius is Point) {
        radius = myRadius;
      }
      addEventListener(Event.ENTER_FRAME, xMoveX);
      addEventListener(Event.ENTER_FRAME, xMoveY);
      addEventListener(Event.ENTER_FRAME, xScale);
      addEventListener(Event.ENTER_FRAME, xUpdate);
    }
    public function stopRotation():void {
      removeEventListener(Event.ENTER_FRAME, xMoveX);
      removeEventListener(Event.ENTER_FRAME, xMoveY);
      removeEventListener(Event.ENTER_FRAME, xScale);
      removeEventListener(Event.ENTER_FRAME, xUpdate);
    }
    private function xMoveX(eventObject:Event=null):void {
      x = center.x+nCos*radius.x;
    }
    private function xMoveY(eventObject:Event=null):void {
      y = center.y+nSin*radius.y;
    }
    private function xScale(eventObject:Event=null):void {
      scaleX = nSin;
    }
    private function xUpdate(eventObject:Event=null):void {
      nRadian += nIncrements;
      nCos = Math.cos(nRadian);
      nSin = Math.sin(nRadian);
    }
  }
}

△MovieClipクラスを継承し、プロパティは自らの値として処理。メソッドsetPosition()を追加し、startRotation()にはオプションの引数を設定。

クラスRotationのコンストラクタには、引数としてタイムラインが渡されます。Rotationインスタンスを動的に生成してステージに表示しますので、そのときの親となるタイムラインが必要だからです。コンストラクタメソッドは、渡されたタイムラインをターゲットにしてDisplayObjectContainer.addChild()メソッドを呼出し、作成されたインスタンスをタイムラインに配置します。

その後、ステージサイズを基準に楕円軌道の中心座標とx軸y軸各方向の半径をPointインスタンスで設定しています。このステートメントの順序は重要です。なぜなら、インスタンスが表示リストに加わっていないと、DisplayObject.stageプロパティはnullになっているからです。ほかに、DisplayObject.rootプロパティも、表示リストに入る前は参照が設定されません。

上記スクリプト2-3-005には、DisplayObjectContainer.addChild()メソッドを呼出す前に、確認としてプロパティDisplayObject.stageDisplayObject.rootを[出力]するtrace()ステートメントが挿入してあります。結果は、ともに値としてnullが表示されます。

[MEMO]2-3-007 trace()関数の引数
ActiolnScript 3.0のtrace()関数には、複数の引数を指定することができます。その場合、値は半角スペース区切りで表示されます。カンマ区切りで表示したいときは、ActionScript 2.0でも使われたテクニックで、配列アクセス演算子[]を使って、値を配列エレメントとして指定すればよいでしょう。

trace(0, 1, 2);   // 出力: 0 1 2
trace([0, 1, 2]);   // 出力: 0,1,2

コンストラクタの最後のステートメントでは、インスタンスの初期位置座標を設定するために、メソッドsetPosition()が呼出されます。setPosition()メソッドは、ActionScript 2.0のスクリプト2-3-005と同じく、メソッドxMoveX()とxMoveY()、およびxScale()を呼出しています。これらのメソッドには、引数が与えられていないことにご注目ください。

呼出される3つのメソッドは、startRotation()メソッドでDisplayObject.enterFrameイベントのリスナー関数として登録されます。したがって、メソッドの定義では、引数にイベントオブジェクトが指定されています。ですから、これらのメソッドを引数なしに呼出せば、当然[コンパイルエラー]が起こります(図2-3-013)。

▲図2-3-013 引数の指定されたメソッドを引数なしに呼出すと[コンパイルエラー]になる
Flash_OOP_2-3-013.gif
△メソッドに指定されている引数ひとつを渡さなければならない。

[コンパイルエラー]を避けるためには、呼出し時に引数を与える必要があります。だからといって、ダミーのEventオブジェクトをつくる必要はありません。一般に、引数に指定されたデータ型以外のオブジェクトを渡すと、型不一致のエラーになります。しかし、nullはオブジェクトの初期値として受取り可能なのです。

したがって、メソッドの呼出し時に引数としてnullを渡せば、[コンパイルエラー]は避けられます。さらに、ActionScript 3.0では、メソッド(関数)の引数にデフォルト値が指定できるようになりました。前記スクリプト2-3-006で、メソッドxMoveX()とxMoveY()、およびxScale()の引数の型指定の後に代入演算子=で指定している値が、そのデフォルト値の設定になります。

3つのメソッドの引数には、デフォルト値としてnullが与えられています。そのため、メソッドを引数なしに呼出しても、引数にはデフォルト値nullが設定され、[コンパイルエラー]が生じなくなります。

メソッドstartRotation()のふたつの引数にも、デフォルト値としてnullが指定されています。これは、ActionScript 2.0のRotationクラス(スクリプト2-3-005)と同じく、引数なしで呼出された場合には、コンストラクタで設定した初期値の中心座標とx軸y軸各方向の半径を用いるようにするためです。メソッド内における条件判定の流れも、基本的には2.0のRotationクラスと同じです。

ただし、Pointインスタンスかどうかを調べるのに、instanceofでなく、ActionScript 3.0から備わったis演算子を用いています。3.0でinstancof演算子を使うと、デフォルトではis演算子に替えるよう警告が表示されます(図2-3-014)。「instanceof演算子は、ECMAScript Edition 3との後方互換性を維持するために提供されて」いるものだからです(ヘルプ[ActionScript 3.0コンポーネントリファレンスガイド]→[演算子]→[instanceof演算子])。

▲図2-3-014 instanceofに替えてis演算子を使うよう警告される
Flash_OOP_2-3-014.gif
instanceof演算子は、ECMAScript 3との下位互換性を保つために提供されている。

[MEMO]2-3-008 is演算子のinstanceof演算子との違い
instanceof演算子は、インスタンスのプロトタイプチェーンをたどって継承の有無を判別します。それに対して、is演算子は、継承(extends定義キーワード)だけでなく、インタフェースの実装(implements定義キーワード)も含めて、データ互換があるかどうかを評価します。

var _mc:MovieClip = new MovieClip();
trace(_mc is DisplayObject);   // 出力: true
trace(_mc instanceof DisplayObject);   // 出力: true
trace(_mc is IEventDispatcher);   // 出力: true
trace(_mc instanceof IEventDispatcher);   // 出力: false

なお、プロトタイプベースの継承については、筆者サイトFumioNonaka.com「ActionScript 2.0と1.0の継承について」(<http://www.fumiononaka.com/TechNotes/Flash/FN0504001.html>)をご参照ください。

2-3-4-3 Spriteクラスの継承
Spriteクラスは、MovieClipのスーパークラスです。MovieClipクラスから、タイムラインの機能を除いたクラスがSpriteになります。一般にスーバークラスは、サブクラスより機能(プロパティやメソッド)が少ない分軽く動きます。ですから、クラスを関連づけたいMovieClipシンボルが1フレームの構成で、タイムラインの機能もとくに必要ない場合には、クラスはSpriteを継承して定義する方が有利です。

MovieClipシンボルに設定した前掲の3D風に回転するクラス(スクリプト2-3-006)をSpriteのサブクラスにするには、つぎのとおり3箇所をMovieClipからSpriteに書替えます。

// ActionScript 3.0クラス定義ファイル: Rotation.as
package {
  import flash.display.Sprite;
  import flash.events.Event;
  import flash.geom.Point;
  public class Rotation extends Sprite {
    private var center:Point;
    private var radius:Point;
    private var nRadian:Number = 0;
    private var nCos:Number = Math.cos(nRadian);
    private var nSin:Number = Math.sin(nRadian);
    private var nIncrements:Number = 0.1;
    public function Rotation(my_mc:Sprite) {

MovieClipシンボルにSpriteを継承したクラスが設定されると、第2フレーム以降は無視され、フレームアクションが書けません。フレームには、コメントのみの記述でも[コンパイルエラー]になります。MovieClipシンボルに設定するクラスの継承は、タイムラインの機能を使うかどうかによって判断する必要があります。

▲図2-3-015 Spriteを直接継承するとフレームアクションが記述できない
Flash_OOP_2-3-015.gif
△コメントだけでも記述すれば[コンパイルエラー]になる。

[Prev/Next]


作成者: 野中文雄
作成日: 2008年2月13日


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