サイトトップ

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

Adobe Flash非公式テクニカルノート

Starlingフレームワークで物理演算エンジンNapeを使う

ID: FN1201005 Product: Flash CS5 and above Platform: All Version: 11 and above/ActionScript 3.0

Starlingフレームワークで、物理演算エンジンを使ってみましょう。CPUの負荷が上がりやすい物理演算は、Stage3Dを試したい処理のひとつです。今回は、使いやすさで注目されている「Nape」を採上げます[*1]

なお、Starlingフレームワークを使うには、Flash Player 11のSWFを書出すことができ、Starlingフレームワークをインストールしなければなりません。まだ済ませていない方は、つぎのふたつのノートをお読みください。また、Starlingフレームワークのごく基本的な使い方は「Starlingフレームワークを使う」に解説しました。

  1. Flash CS5/CS5.5でFlash Player 11のSWFを書出す
  2. Starlingフレームワークをインストールする

サンプルSWF
*サンプルSWF

[*1] NapeとStarlingフレームワークについては、CODE FLOOD「Nape and Starling together? It's possible!」(英文)をご参照ください。


01 Napeをインストールする
Napeはオープンソースの物理エンジンです。ダウンロードページから、SWCファイルが手に入ります(図001)。Starlingフレームワークには、「AS3」のFlash Player 10以降(fp10+)のSWCを選びます。debug版とrelease版とは、本稿のスクリプトを試すには、どちらでも構いません。

図001■ダウンロードページにリンクされた「AS3」用のSWCファイル
図001

ダウンロードしたSWCファイルはライブラリパスに保存します。Starlingフレームワークで設定したライブラリパスのフォルダに一緒に入れて構いません(前出「Starlingフレームワークをインストールする」03「[パブリッシュ設定]によるActionScriptファイルのパスの指定」の項参照)。


02 Starlingフレームワークで[ライブラリ]のビットマップをステージに表示する
Napeの説明に入る前に、道具立てを整えなければなりません。アニメーションはボールを落として弾ませますので、そのボールを用意しましょう。Flashの[ライブラリ]に背景が抜かれたボール用の円形のビットマップを納め、クラスを設定しておきます(図002)。

図002■[ライブラリ]に用意したボール用ビットマップにクラスを設定する
図002

さて、この[ライブラリ]のビットマップをどうやってStarlingフレームワークにもっていくかです。というのは、前出「Starlingフレームワークを使う」01「Starlingフレームワークで何からつくり始めるか」に述べたとおり、「Starlingフレームワークの表示リストには、Starlingの表示オブジェクトしか加えられません」。そこで使うのが、Textureクラスです。

テクスチャ(texture)というのは、Starlingフレームワークでイメージを表示するために用いられるビットマップデータのことです。TextureクラスにはTexture.fromBitmapData()という静的メソッドが備わっていて、BitmapDataオブジェクトからTextureインスタンスがつくれます。

もっとも、Textureインスタンスは表示オブジェクトではないので、そのままではStarlingフレームワークのステージには置けません。Imageクラスのコンストラクタメソッドに引数として渡したうえで、そのImageオブジェクトを表示リストに加えます。

var myTexture:Texture = Texture.fromBitmapData(BitmapDataオブジェクト);
var myImage:Image = new Image(myTexture);
addChild(myImage);

Starlingフレームワークの表示リスト最上位(ルート)に置く表示オブジェクトとして以下のクラス(MySprite)を定めると(スクリプト001)、[ライブラリ]にクラス(Pen)を設定したビットマップがステージの中央に表示されます。なお、FLAファイル(ASファイルと同階層)には、つぎのフレームアクションが書かれているものとします(前出「Starlingフレームワークを使う」03「Starlingフレームワークの初期化」スクリプト002参照)。

// フレームアクション: メインタイムライン
import starling.core.Starling;
var myStarling:Starling = new Starling(MySprite, stage);
myStarling.start();

スクリプト001■[ライブラリ]のビットマップからテクスチャをつくって表示する
    // ActionScript 3.0クラス定義ファイル: MySprite.as
  1. package {
  2.   import flash.display.BitmapData;
  3.   import starling.display.Sprite;
  4.   import starling.display.Image;
  5.   import starling.textures.Texture;
  6.   import starling.events.Event;
  7.   public class MySprite extends Sprite {
  8.     private var nWidth:int;
  9.     private var nHeight:int;
  10.     private var nRadius:Number;
  11.     private var myBitmapData:BitmapData;
  12.     private var myTexture:Texture;
  13.     public function MySprite() {
  14.       addEventListener(Event.ADDED_TO_STAGE, initialize);
  15.     }
  16.     private function initialize(eventObject:Event):void {
  17.       nWidth = stage.stageWidth;
  18.       nHeight = stage.stageHeight;
  19.       myBitmapData = new Pen();
  20.       myTexture = Texture.fromBitmapData(myBitmapData);
  21.       nRadius = myBitmapData.width / 2;
  22.       setBall();
  23.     }
  24.     private function setBall():void {
  25.       var myImage:Image = createBall();
  26.       addChild(myImage);
  27.       myImage.x = nWidth / 2;
  28.       myImage.y = nHeight / 2;
  29.     }
  30.     private function createBall():Image {
  31.       var myImage:Image = new Image(myTexture);
  32.       myImage.pivotX = nRadius;
  33.       myImage.pivotY = nRadius;
  34.       return myImage;
  35.     }
  36.   }
  37. }

図003■[ライブラリ]のビットマップがStarlingフレームワークでステージ中央に表示された
図003

初期設定の関数(initialize())は、[ライブラリ]のビットマップに設定したクラスからインスタンスをつくり、Texture.fromBitmapData()メソッドの引数に渡してTextureインスタンスをつくっています(スクリプト001第19〜20行目)。

ボールのインスタンスをつくる関数(setBall())は別に定めました。後のNapeを使ったサンプルでは、ボールのインスタンスを複数つくるためです。Imageインスタンスはもうひとつの関数(createBall())を呼出してつくり、表示リストに加えて、位置を定めました(スクリプト001第25〜28行目)。


03 剛体をつくる − Bodyクラス
前項02で「Starlingフレームワークで[ライブラリ]のビットマップをステージに表示する」ことができました。けれど、このビットマップ、Starlingフレームワークの用語ではテクスチャそのものは、物理演算の対象にはなりません。

Napeで新たに物体を定めて、物理空間に置いたうえで、動かす力を加えて計算しなければならないのです。そして、これらはあくまで目に見えない物理演算です。必要な計算がすべてできた暁に、結果をテクスチャに反映することで、ようやくアニメーションとして見ることができるのです。

まずは、物体としてボールをつくります。物理エンジンで扱う物体は「剛体(Rigid body)」と呼ばれ[*2]Bodyクラスで定めます。コンストラクタメソッドのシンタックスは、つぎのとおりです。

new Body(種類, 位置)

引数のうち、種類はBodyTypeクラスの定数で渡します。そして、動かす剛体は、BodyType.DYNAMICにします。位置は、Napeに備わる2次元ベクトルのクラスVec2で定めます。コンストラクタには引数として(x, y)座標を渡します。

剛体のかたちは、Body.shapesプロパティadd()メソッドで、Shapeオブジェクトとして加えます。円形はCircleクラスで、コンストラクタメソッドのシンタックスはつぎのとおりです。

new Circle(半径, 重心, 材質)

半径は数値で、ボールのテクスチャに合わせます。重心はVec2オブジェクトです。とくに定めないときはnullを渡せば結構です。材質はMaterialオブジェクトで、コンストラクタの第1引数には弾性を数値で定めます。

そして、テクスチャをもつImageオブジェクトは、Body.graphicプロパティに納めます。前掲スクリプト001に加える剛体のBodyオブジェクトをつくる処理はつぎのようになります。ボールの剛体は新たな関数(addPhysics())を定めてつくりました。

import nape.phys.Body;
import nape.phys.BodyType;
import nape.phys.Material;
import nape.geom.Vec2;
import nape.shape.Circle;

public class MySprite extends Sprite {
  
  private var myBody:Body;

  private function setBall():void {
    var myImage:Image = createBall();
    addChild(myImage);
    addPhysics(myImage);   // 追加
    myImage.x = nWidth / 2;
    myImage.y = nHeight / 2;
  }

  // 物理演算の剛体を定める関数定義
  private function addPhysics(myImage:Image, nX:Number = 0, nY:Number = 0) {
    myBody = new Body(BodyType.DYNAMIC, new Vec2(nX, nY));
    myBody.shapes.add(new Circle(nRadius, null, new Material(20)));
    myBody.graphic = myImage;
  }
}

なお、ボールのBodyオブジェクトをつくる関数(addPhysics())には、引数としてImageオブジェクトに加えて、(x, y)座標も定め、そのデフォルト値はともに0としました。

[*2] 剛体については、「Flash3D/衝突・剛体・加速・固定」の「剛体」をご参照ください。


04 物理空間を定める − Spaceクラス
Napeで剛体のBodyオブジェクトをつくったら、つぎにそれを物理空間に置きます。物理空間を定めるのはSpaceクラスです。コンストラクタメソッドの第1引数には、重力をVec2オブジェクトで定めます。

new Space(重力)

物理空間のSpaceオブジェクトは、剛体のBody.spaceプロパティに設定します。そのため、ボールをつくる関数(setBall())と物理演算を定める関数(addPhysics())には、Spaceオブジェクトを引数に渡しましょう。前掲スクリプト001には、さらにつぎのように手を加えます。

import nape.space.Space;

public class MySprite extends Sprite {

  private var mySpace:Space;
  private function initialize(eventObject:Event):void {
    // ...[中略]...
    mySpace = new Space(new Vec2(0, 2000));   // 追加
    // setBall();
    setBall(mySpace);   // 修正
  }
  // private function setBall():void {
  private function setBall(mySpace:Space):void {   // 修正
    var myImage:Image = createBall();
    addChild(myImage);
    // addPhysics(myImage);
    addPhysics(myImage, mySpace, nWidth / 2);
    // ...[中略]...
  }
  // 物理演算の剛体と空間を定める関数定義
  // private function addPhysics(myImage:Image, nX:Number = 0, nY:Number = 0) {
  private function addPhysics(myImage:Image, mySpace:Space, nX:Number = 0, nY:Number = 0) {   // 修正
    myBody = new Body(BodyType.DYNAMIC, new Vec2(nX, nY));
    myBody.shapes.add(new Circle(nRadius, null, new Material(20)));
    myBody.graphic = myImage;
    myBody.space = mySpace;   // 追加
  }
}


05 物理演算をイメージに反映させる
剛体のBodyオブジェクトをつくり、物理空間をSpaceオブジェクトで定めたら、いよいよ物理空間の剛体に力を加えて動かしましょう。そのためには、Space.step()メソッドで演算を進めます。

Spaceオブジェクト.step(経過時間)

物理演算は目に見えません。つまり、表示オブジェクトと異なり、画面の再描画で処理が進むという決まりもありません。そのため、Space.step()メソッドで、指定時間分の演算を進めるのです。もっとも、メソッドはEvent.ENTER_FRAMEイベントで呼出すことにしますので、進める時間はフレームレートの逆数でよいでしょう。

そして、演算結果をテクスチャのImageオブジェクトに反映させて、ようやくアニメーションにお目にかかれます。Bodyオブジェクトに動きがあると、Body.graphicUpdateプロパティに設定したコールバック関数が呼出されます。

剛体の動きは、具体的にはBody.positionBody.rotationプロバティの変化になります。これらの値をBody.graphicプロパティのImageオブジェクトに設定することにより、物理的な動きがアニメーションとして表れます。

private function initialize(eventObject:Event):void {
  // ...[中略]...
  mySpace = new Space(new Vec2(0, 2000));
  setBall(mySpace);
  addEventListener(Event.ENTER_FRAME, refreshScreen);   // 追加
}

// 物理演算を進めるリスナー関数定義
private function refreshScreen(eventObject:Event):void {
  mySpace.step(1/24);
}

private function addPhysics(myImage:Image, mySpace:Space, nX:Number = 0, nY:Number = 0) {
  var myBody:Body = new Body(BodyType.DYNAMIC, new Vec2(nX, nY));
  myBody.shapes.add(new Circle(nRadius, null, new Material(20)));
  myBody.graphic = myImage;
  myBody.space = mySpace;
  myBody.graphicUpdate = updateGraphic;   // 追加
}

// 剛体の動きをイメージに反映させる関数定義
private function updateGraphic(myBody:Body):void {
  var myImage:Image = myBody.graphic as Image;
  var myPosition:Vec2 = myBody.position;
  myImage.x = myPosition.x;
  myImage.y = myPosition.y;
  myImage.rotation = myBody.rotation;
}

Body.graphicUpdateプロパティに定めたコールバック関数(updateGraphic())は、Body.graphicプロパティからImageオブジェクトを取出し、Body.positionBody.rotationプロバティの(x, y)座標と回転角をオブジェクトに設定しています。

できあがったクラス定義(MySprite)の全体は、以下のスクリプト002のとおりです。これで晴れて物理シミュレーションにもとづいて、ボールのイメージがステージ上を自然落下します(図004)。[パブリッシュプレビュー]で確かめてみましょう。

スクリプト002■物理空間に置いた剛体が自然落下するアニメーション
    // ActionScript 3.0クラス定義ファイル: MySprite.as
  1. package {
  2.   import flash.display.BitmapData;
  3.   import starling.display.Sprite;
  4.   import starling.display.Image;
  5.   import starling.textures.Texture;
  6.   import starling.events.Event;
  7.   import nape.phys.Body;
  8.   import nape.phys.BodyType;
  9.   import nape.phys.Material;
  10.   import nape.geom.Vec2;
  11.   import nape.shape.Circle;
  12.   import nape.space.Space;
  13.   public class MySprite extends Sprite {
  14.     private var nWidth:int;
  15.     private var nHeight:int;
  16.     private var nRadius:Number;
  17.     private var myBitmapData:BitmapData;
  18.     private var myTexture:Texture;
  19.     private var myBody:Body;
  20.     private var mySpace:Space;
  21.     public function MySprite() {
  22.       addEventListener(Event.ADDED_TO_STAGE, initialize);
  23.     }
  24.     private function initialize(eventObject:Event):void {
  25.       nWidth = stage.stageWidth;
  26.       nHeight = stage.stageHeight;
  27.       myBitmapData = new Pen();
  28.       myTexture = Texture.fromBitmapData(myBitmapData);
  29.       nRadius = myBitmapData.width / 2;
  30.       mySpace = new Space(new Vec2(0, 2000));
  31.       setBall(mySpace);
  32.       addEventListener(Event.ENTER_FRAME, refreshScreen);
  33.     }
  34.     private function refreshScreen(eventObject:Event):void {
  35.       mySpace.step(1/24);
  36.     }
  37.     private function setBall(mySpace:Space):void {
  38.       var myImage:Image = createBall();
  39.       addChild(myImage);
  40.       addPhysics(myImage, mySpace, nWidth / 2);
  41.     }
  42.     private function createBall():Image {
  43.       var myImage:Image = new Image(myTexture);
  44.       myImage.pivotX = nRadius;
  45.       myImage.pivotY = nRadius;
  46.       return myImage;
  47.     }
  48.     private function addPhysics(myImage:Image, mySpace:Space, nX:Number = 0, nY:Number = 0) {
  49.       myBody = new Body(BodyType.DYNAMIC, new Vec2(nX, nY));
  50.       myBody.shapes.add(new Circle(nRadius, null, new Material(20)));
  51.       myBody.graphic = myImage;
  52.       myBody.space = mySpace;
  53.       myBody.graphicUpdate = updateGraphic;
  54.     }
  55.     private function updateGraphic(myBody:Body):void {
  56.       var myImage:Image = myBody.graphic as Image;
  57.       var myPosition:Vec2 = myBody.position;
  58.       myImage.x = myPosition.x;
  59.       myImage.y = myPosition.y;
  60.       myImage.rotation = myBody.rotation;
  61.     }
  62.   }
  63. }

図004■ボールのイメージがステージ上を落下する
図004

なお、ボールのImageオブジェクトの座標はNapeが計算しますので、スクリプト001のポールをつくる関数(setBall())からインスタンスの初期位置の定めは除きました。

private function setBall(mySpace:Space):void {
  var myImage:Image = createBall();
  addChild(myImage);
  addPhysics(myImage, mySpace, nWidth / 2);
  // myImage.x = nWidth / 2;
  // myImage.y = nHeight / 2;

}


06 固定された剛体を置く
ボールがステージの下まで落ちてしまわないように床を置きましょう。やはり、剛体のBodyオブジェクトをつくります。ただし、床は動きませんので、コンストラクタメソッドに渡す第1引数の種類はBodyType.STATICです。

床は矩形ですので、Polygonクラスでつくります。引数は配列で、エレメントには4角の座標をVec2オブジェクトで定めます。もっとも、(x, y)座標に幅と高さを渡せばその配列をつくってくれる静的メソッドPolygon.rect()が用意されています。

床をつくる関数(setFloor())は、ボールをつくる関数(setBall())と同じように、Spaceオブジェクトを引数にして初期化の関数(initialize())から呼出すことにします。

import nape.shape.Polygon;

public class MySprite extends Sprite {

  private var bodyStatic:Body;

  private function initialize(eventObject:Event):void {
    nWidth = stage.stageWidth;
    nHeight = stage.stageHeight;
    // ...[中略]...
    mySpace = new Space(new Vec2(0, 2000));
    setBall(mySpace);
    setFloor(mySpace);   // 追加
    addEventListener(Event.ENTER_FRAME, refreshScreen);
  }

  // 固定した矩形の剛体を定める関数定義
  private function setFloor(mySpace:Space):void {
    var myPolygon:Polygon = new Polygon(Polygon.rect(0, nHeight, nWidth, 100));
    bodyStatic = new Body(BodyType.STATIC);
    bodyStatic.shapes.add(myPolygon);
    bodyStatic.space = mySpace;
  }    
}

床の剛体が矩形(Polygonオブジェクト)で固定(BodyType.STATIC)されているというだけで、手順はボールの剛体をつくるのと同じです。なお、Body()コンストラクタに第2引数の座標(Vec2オブジェクト)を渡さないと、デフォルトの座標(0, 0)とみなされます。

スクリプト003■動的な剛体が落下して静的な剛体のうえで弾むアニメーション
    // ActionScript 3.0クラス定義ファイル: MySprite.as
  1. package {
  2.   import flash.display.BitmapData;
  3.   import starling.display.Sprite;
  4.   import starling.display.Image;
  5.   import starling.textures.Texture;
  6.   import starling.events.Event;
  7.   import nape.phys.Body;
  8.   import nape.phys.BodyType;
  9.   import nape.phys.Material;
  10.   import nape.geom.Vec2;
  11.   import nape.shape.Circle;
  12.   import nape.shape.Polygon;
  13.   import nape.space.Space;
  14.   public class MySprite extends Sprite {
  15.     private var nWidth:int;
  16.     private var nHeight:int;
  17.     private var nRadius:Number;
  18.     private var myBitmapData:BitmapData;
  19.     private var myTexture:Texture;
  20.     private var myBody:Body;
  21.     private var bodyStatic:Body;
  22.     private var mySpace:Space;
  23.     public function MySprite() {
  24.       addEventListener(Event.ADDED_TO_STAGE, initialize);
  25.     }
  26.     private function initialize(eventObject:Event):void {
  27.       nWidth = stage.stageWidth;
  28.       nHeight = stage.stageHeight;
  29.       myBitmapData = new Pen();
  30.       myTexture = Texture.fromBitmapData(myBitmapData);
  31.       nRadius = myBitmapData.width / 2;
  32.       mySpace = new Space(new Vec2(0, 2000));
  33.       setBall(mySpace);
  34.       setFloor(mySpace);
  35.       addEventListener(Event.ENTER_FRAME, refreshScreen);
  36.     }
  37.     private function refreshScreen(eventObject:Event):void {
  38.       mySpace.step(1/24);
  39.     }
  40.     private function setBall(mySpace:Space):void {
  41.       var myImage:Image = createBall();
  42.       addChild(myImage);
  43.       addPhysics(myImage, mySpace, nWidth / 2);
  44.     }
  45.     private function createBall():Image {
  46.       var myImage:Image = new Image(myTexture);
  47.       myImage.pivotX = nRadius;
  48.       myImage.pivotY = nRadius;
  49.       return myImage;
  50.     }
  51.     private function addPhysics(myImage:Image, mySpace:Space, nX:Number = 0, nY:Number = 0) {
  52.       myBody = new Body(BodyType.DYNAMIC, new Vec2(nX, nY));
  53.       myBody.shapes.add(new Circle(nRadius, null, new Material(20)));
  54.       myBody.graphic = myImage;
  55.       myBody.space = mySpace;
  56.       myBody.graphicUpdate = updateGraphic;
  57.     }
  58.     private function updateGraphic(myBody:Body):void {
  59.       var myImage:Image = myBody.graphic as Image;
  60.       var myPosition:Vec2 = myBody.position;
  61.       myImage.x = myPosition.x;
  62.       myImage.y = myPosition.y;
  63.       myImage.rotation = myBody.rotation;
  64.     }
  65.     private function setFloor(mySpace:Space):void {
  66.       var myPolygon:Polygon = new Polygon(Polygon.rect(0, nHeight, nWidth, 100));
  67.       bodyStatic = new Body(BodyType.STATIC);
  68.       bodyStatic.shapes.add(myPolygon);
  69.       bodyStatic.space = mySpace;
  70.     }    
  71.   }
  72. }

[パブリッシュプレビュー]を確かめると、落下したボールはステージ下端を床にして跳ね返り、バウンドを繰返します。


07 落下する剛体を増やす
では仕上げに、ボールの数を100個ほどに増やしてみましょう。Timerオブジェクトを使って、時間差でボールを落とします。Bodyオブジェクトの数が増えますので、Vectorインスタンス(Bodyベース型)にまとめることにします。加える処理は、もうStarlingフレームワークもNapeも関わりません。ですから、細かい解説は省きます。手を入れるところはつぎのとおりです。

import flash.utils.Timer;
import flash.events.TimerEvent;

public class MySprite extends Sprite {
  // private var myBody:Body;
  private var bodies:Vector.<Body> = new Vector.<Body>();   // 修正
  private var myTimer:Timer = new Timer(200, 100);   // 追加

  private function initialize(eventObject:Event):void {
    // ...[中略]...
    // setBall(mySpace);
  // 削除
    setFloor(mySpace);
    addEventListener(Event.ENTER_FRAME, refreshScreen);
    // 以下を追加
    myTimer.addEventListener(TimerEvent.TIMER, addBall);
    myTimer.start();
  }
  // Timer.timerイベントのリスナー関数定義
  function addBall(eventObject:TimerEvent):void {
    setBall(mySpace);
  }

  private function setBall(mySpace:Space):void {
    var myImage:Image = createBall();
    addChild(myImage);
    // addPhysics(myImage, mySpace, nWidth / 2);
    addPhysics(myImage, mySpace, nWidth * Math.random());   // 修正
  }

  private function addPhysics(myImage:Image, mySpace:Space, nX:Number = 0, nY:Number = 0) {
    // myBody = new Body(BodyType.DYNAMIC, new Vec2(nX, nY));
    var myBody:Body = new Body(BodyType.DYNAMIC, new Vec2(nX, nY));   // 修正
    myBody.shapes.add(new Circle(nRadius, null, new Material(20)));
    myBody.graphic = myImage;
    myBody.space = mySpace;
    myBody.graphicUpdate = updateGraphic;
    bodies.push(myBody);   // 追加
  }
}

Timerオブジェクトのリスナー関数(addBall())を用いて、0.2秒間隔でランダムな水平位置からボールが計100個落とされます(図005)。できあがったクラス定義は、スクリプト004のとおりです。

スクリプト004■動的な剛体をつぎつぎに落として互いに弾ませるアニメーション
    // ActionScript 3.0クラス定義ファイル: MySprite.as
  1. package {
  2.   import flash.display.BitmapData;
  3.   import flash.utils.Timer;
  4.   import flash.events.TimerEvent;
  5.   import starling.display.Sprite;
  6.   import starling.display.Image;
  7.   import starling.textures.Texture;
  8.   import starling.events.Event;
  9.   import nape.phys.Body;
  10.   import nape.phys.BodyType;
  11.   import nape.phys.Material;
  12.   import nape.geom.Vec2;
  13.   import nape.shape.Circle;
  14.   import nape.shape.Polygon;
  15.   import nape.space.Space;
  16.   public class MySprite extends Sprite {
  17.     private var nWidth:int;
  18.     private var nHeight:int;
  19.     private var nRadius:Number;
  20.     private var myBitmapData:BitmapData;
  21.     private var myTexture:Texture;
  22.     private var bodies:Vector.<Body> = new Vector.<Body>();
  23.     private var bodyStatic:Body;
  24.     private var mySpace:Space;
  25.     private var myTimer:Timer = new Timer(200, 100);
  26.     public function MySprite() {
  27.       addEventListener(Event.ADDED_TO_STAGE, initialize);
  28.     }
  29.     private function initialize(eventObject:Event):void {
  30.       nWidth = stage.stageWidth;
  31.       nHeight = stage.stageHeight;
  32.       myBitmapData = new Pen();
  33.       myTexture = Texture.fromBitmapData(myBitmapData);
  34.       nRadius = myBitmapData.width / 2;
  35.       mySpace = new Space(new Vec2(0, 2000));
  36.       setFloor(mySpace);
  37.       addEventListener(Event.ENTER_FRAME, refreshScreen);
  38.       myTimer.addEventListener(TimerEvent.TIMER, addBall);
  39.       myTimer.start();
  40.     }
  41.     function addBall(eventObject:TimerEvent):void {
  42.       setBall(mySpace);
  43.     }
  44.     private function refreshScreen(eventObject:Event):void {
  45.       mySpace.step(1/24);
  46.     }
  47.     private function setBall(mySpace:Space):void {
  48.       var myImage:Image = createBall();
  49.       addChild(myImage);
  50.       addPhysics(myImage, mySpace, nWidth * Math.random());
  51.     }
  52.     private function createBall():Image {
  53.       var myImage:Image = new Image(myTexture);
  54.       myImage.pivotX = nRadius;
  55.       myImage.pivotY = nRadius;
  56.       return myImage;
  57.     }
  58.     private function addPhysics(myImage:Image, mySpace:Space, nX:Number = 0, nY:Number = 0) {
  59.       var myBody:Body = new Body(BodyType.DYNAMIC, new Vec2(nX, nY));
  60.       myBody.shapes.add(new Circle(nRadius, null, new Material(20)));
  61.       myBody.graphic = myImage;
  62.       myBody.space = mySpace;
  63.       myBody.graphicUpdate = updateGraphic;
  64.       bodies.push(myBody);
  65.     }
  66.     private function updateGraphic(myBody:Body):void {
  67.       var myImage:Image = myBody.graphic as Image;
  68.       var myPosition:Vec2 = myBody.position;
  69.       myImage.x = myPosition.x;
  70.       myImage.y = myPosition.y;
  71.       myImage.rotation = myBody.rotation;
  72.     }
  73.     private function setFloor(mySpace:Space):void {
  74.       var myPolygon:Polygon = new Polygon(Polygon.rect(0, nHeight, nWidth, 100));
  75.       bodyStatic = new Body(BodyType.STATIC);
  76.       bodyStatic.shapes.add(myPolygon);
  77.       bodyStatic.space = mySpace;
  78.     }    
  79.   }
  80. }

図005■ランダムな水平位置から時間差で100個のボールが落とされて互いにぶつかり合う
図005

Starlingフレームワークに関わる説明をひとつだけ加えます。前掲スクリプト004では、ボールと同じ数だけImageとBodyオブジェクトが増えました。けれど、[ライブラリ]のビットマップからつくったTextureインスタンスはひとつで、それをすべてのImageオブジェクトが共有しています。

データが大きくなりやすいテクスチャは、複数の表示オブジェクトで使い回すことにより、メモリの消費を抑えることができるのです。Imageオブジェクトがステージに数多く表れても、実態は容れ物にすぎません。使われるTextureインスタンスのデータを管理することが重要です。


作成者: 野中文雄
作成日: 2012年1月23日


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