サイトトップ

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

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

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

ID: FN1304001 Product: Flash CS6 and above Platform: All Version: 11 and above/ActionScript 3.0

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

  • サンプルファイル: スクリプト004(CS6形式/Zip圧縮/約29KB)
    • StarlingフレームワークとNapeのインストールおよび設定が必要です。

[*1] 本稿は、2012年12月13日にリリースされたバージョン2.0.3にもとづきます。以前のバージョンについては旧稿をご参照ください。


01 Napeをインストールする

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

図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クラス

物理シミュレーションになじみのない方のために、その考え方を先にご説明します。物理演算は計算ですので、結果は数値です。そのままでは、アニメーションが再生されたりはしません。演算した数値を、ステージに描いたオブジェクトのプロパティに当てはめて、初めてオブジェクトの動きとして目に見えるのです。

ステージのオブジェクトは、いわば操り人形です。Box2Dが黒子のように、自分自身の姿は隠し、オブジェクトを操って物理シミュレーションを演じて見せるということです。

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

new Body(種類, 位置)

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

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

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

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

ここまでは、黒子である物理世界の定めです。人形のテクスチャをもつImageオブジェクトと、関わりができていません。人形のオブジェクトとの絡みはInteractor.userDataプロパティに与えます。このプロパティはダイナミックなオブジェクトですので、どのようなデータでもプロパティとして納められます。そこで、Imageオブジェクトの参照をBody.userDataプロパティに加えます。

前掲スクリプト001に加える剛体のBodyオブジェクトをつくる処理はつぎのようになります。ボールの剛体は新たな関数(addPhysics())を定めてつくりました。なお、ステートメント行頭の番号は、後に全体を掲げるスクリプト002にもとづきます。

  1. import nape.phys.Body;
  2. import nape.phys.BodyType;
  1. import nape.phys.Material;
  2. import nape.geom.Vec2;
  3. import nape.shape.Circle;
  4. import nape.space.Space;
  5. public class MySprite extends Sprite {
      // private function setBall():void {
  1.   private function setBall(mySpace:Space):void {
  2.     var myImage:Image = createBall();
  1.     addPhysics(myImage, mySpace, nWidth / 2);
  2.   }
  1.   private function addPhysics(myImage:Image, mySpace:Space, nX:Number = 0, nY:Number = 0) {
  2.     Var myBody:Body = new Body(BodyType.DYNAMIC, new Vec2(nX, nY));
  3.     myBody.shapes.add(new Circle(nRadius, null, new Material(20)));
  4.     myBody.userData.graphic = myImage;
  1.   }

ボールのBodyオブジェクトをつくる関数(addPhysics())には、第1引数としてImageオブジェクト、第3および第4引数にxy座標を定め、座標のデフォルト値はともに0としました(第57行目)。第2引数のSpaceオブジェクトは、呼出す側のメソッド(setBall())が受取った引数をそのまま渡しています(第46および第49行目)。Spaceオブジェクトについては、項を改めてご説明します。

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


04 物理空間を定める − Spaceクラス

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

new Space(重力)

剛体を物理空間に置くには、Spaceオブジェクトを剛体のBody.spaceプロパティに与えます。前掲スクリプト001には、さらにつぎのような手を加えます。ボールをつくるメソッド(setBall())の引数にSpaceオブジェクトを与え(第30〜31および第46行目)、物理演算を定めるメソッド(addPhysics())に渡すことにします(第49および第57行目)。

  1.   import nape.space.Space;
  2.   public class MySprite extends Sprite {
  1.     private var mySpace:Space;
  1.     private function initialize(eventObject:Event):void {
  1.       mySpace = new Space(new Vec2(0, 2000));
  2.       setBall(mySpace);
  1.     }
  1.     private function setBall(mySpace:Space):void {
  2.       var myImage:Image = createBall();
  1.       addPhysics(myImage, mySpace, nWidth / 2);
  2.     }
  1.     private function addPhysics(myImage:Image, mySpace:Space, nX:Number = 0, nY:Number = 0) {
  1.       myBody.space = mySpace;
  1.     }

05 物理演算をイメージに反映させる

剛体のBodyオブジェクトをつくり、物理空間をSpaceオブジェクトで定めたら、初めの状態が決まりました。物理空間における剛体の動きをシミュレートするには、物理空間の時間を進めなければなりません。そのためには、Space.step()メソッドに経過時間を与えます。

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

物理演算シミュレーションは、Flashの表示オブジェクト(DisplayObject)と異なり、画面の再描画で処理が進むという決まりはありません。アニメーションとして見せたいなら、フレームの描画のたびにSpace.step()メソッドで演算を進めなければならないのです。そこで、メソッドはEvent.ENTER_FRAMEイベントで呼出すことにします。すると、進める時間はフレームレートの逆数です。

もっとも、これだけでは黒子であるNapeの中の計算に過ぎません。黒子の演算結果をImageオブジェクトに反映させて、ようやく人形が動き始めます。物理空間でBodyオブジェクトをシミュレートした変化は、メソッドを定めてImageオブジェクトの動きに移します。そして、そのメソッドはBody.userDataプロパティにコールバックとして加えます。

後掲スクリプト002は、つぎのようにEvent.ENTER_FRAMEイベントのリスナーメソッド(refreshScreen())から、Space.step()メソッドを呼出しています(第35行目)。また、物理演算を定めるメソッド(addPhysics())の中で、Body.userDataプロパティにコールバック(graphicUpdate)を与えました(第62行目)。このメソッド(updateGraphic())は、これから新たに定義します。

  1. private var mySpace:Space;
  1. private function initialize(eventObject:Event):void {
  1.   mySpace = new Space(new Vec2(0, 2000));
  1.   addEventListener(Event.ENTER_FRAME, refreshScreen);
  2. }
  3. private function refreshScreen(eventObject:Event):void {
  4.   mySpace.step(1/24);
  1. }
  1. private function addPhysics(myImage:Image, mySpace:Space, nX:Number = 0, nY:Number = 0) {
  1.   myBody.userData.graphic = myImage;
  1.   myBody.userData.graphicUpdate = updateGraphic;
  2. }

人形のオブジェクトを動かすコールバックの呼出しは、Event.ENTER_FRAMEイベントのリスナーメソッド(refreshScreen())がSpace.step()メソッドで物理演算を進めた後に加えます[*3]

物理空間(mySpace)には、剛体がいくつも含まれているかもしれません。それらは、Space.liveBodies()メソッドでBodyListオブジェクトとして得られます(第36行目)。その中にあるBodyオブジェクトの数は、BodyList.lengthプロパティで調べます(第37行目)。

そして、forステートメントで、すべての人形を順に動かしていきます(第38〜44行目)。BodyList.at()は、引数に渡したインデックスのBodyオブジェクトを取出します(第39行目)。もっとも、剛体がすべて動くとはかぎりません。中には大道具のようなBodyオブジェクトもあります。動かない人形には、コールバックを定めません。したがって、コールバックのプロパティ名(graphicUpdate)を決めておき、あったらそれを呼出します(第40〜43行目)。

コールバックのメソッド(updateGraphic())は、使い回せるようにBodyオブジェクトを引数に受取ります(第42および第64行目)。そのBody.userDataプロパティから動かす人形のオブジェクトを取出し、剛体の動きを移し替えます(第65行目)。剛体の位置はBody.position、回転角はBody.rotationプロバティで調べ、これらの値をImageオブジェクトに当てると、物理演算がアニメーションとして表れます(第66〜69行目)。

  1.   import nape.phys.BodyList;
  1. private function refreshScreen(eventObject:Event):void {
  2.   mySpace.step(1/24);
  3.   var bodies:BodyList = mySpace.liveBodies;
  4.   var nBodies:int = bodies.length;
  5.   for (var i:int = 0; i < nBodies; i++) {
  6.     var myBody:Body = bodies.at(i);
  7.     var update:Function = myBody.userData.graphicUpdate as Function;
  8.     if (Boolean(update)) {
  9.       update(myBody);
  10.     }
  11.   }
  12. }
  1. private function addPhysics(myImage:Image, mySpace:Space, nX:Number = 0, nY:Number = 0) {
  1.   myBody.userData.graphic = myImage;
  1.   myBody.userData.graphicUpdate = updateGraphic;
  2. }
  3. private function updateGraphic(myBody:Body):void {
  4.   var myImage:Image = myBody.userData.graphic as Image;
  5.   var myPosition:Vec2 = myBody.position;
  6.   myImage.x = myPosition.x;
  7.   myImage.y = myPosition.y;
  8.   myImage.rotation = myBody.rotation;
  9. }

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

図004■ボールのイメージがステージ上を落下する
図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.BodyList;
  10.   import nape.phys.Material;
  11.   import nape.geom.Vec2;
  12.   import nape.shape.Circle;
  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 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.       var bodies:BodyList = mySpace.liveBodies;
  37.       var nBodies:int = bodies.length;
  38.       for (var i:int = 0; i < nBodies; i++) {
  39.         var myBody:Body = bodies.at(i);
  40.         var update:Function = myBody.userData.graphicUpdate as Function;
  41.         if (Boolean(update)) {
  42.           update(myBody);
  43.         }
  44.       }
  45.     }
  46.     private function setBall(mySpace:Space):void {
  47.       var myImage:Image = createBall();
  48.       addChild(myImage);
  49.       addPhysics(myImage, mySpace, nWidth / 2);
  50.     }
  51.     private function createBall():Image {
  52.       var myImage:Image = new Image(myTexture);
  53.       myImage.pivotX = nRadius;
  54.       myImage.pivotY = nRadius;
  55.       return myImage;
  56.     }
  57.     private function addPhysics(myImage:Image, mySpace:Space, nX:Number = 0, nY:Number = 0) {
  58.       var myBody:Body = new Body(BodyType.DYNAMIC, new Vec2(nX, nY));
  59.       myBody.shapes.add(new Circle(nRadius, null, new Material(20)));
  60.       myBody.userData.graphic = myImage;
  61.       myBody.space = mySpace;
  62.       myBody.userData.graphicUpdate = updateGraphic;
  63.     }
  64.     private function updateGraphic(myBody:Body):void {
  65.       var myImage:Image = myBody.userData.graphic as Image;
  66.       var myPosition:Vec2 = myBody.position;
  67.       myImage.x = myPosition.x;
  68.       myImage.y = myPosition.y;
  69.       myImage.rotation = myBody.rotation;
  70.     }
  71.   }
  72. }

[*3] 以前のバージョンでは、Space.step()メソッドを呼出せば、Bodyオブジェクトに定めたコールバックも呼ばれました。新バージョンは物理演算エンジンの標準的な仕組みを採入れ、Spaceオブジェクトに含まれるBodyオブジェクトを取出して扱うようになりました。


06 固定された剛体を置く

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

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

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

  1. import nape.shape.Polygon;
  1. public class MySprite extends Sprite {
  1.   private function initialize(eventObject:Event):void {
  1.     mySpace = new Space(new Vec2(0, 2000));
  2.     setBall(mySpace);
  3.     setFloor(mySpace);
  1.   }
  1.   private function setFloor(mySpace:Space):void {
  2.     var myPolygon:Polygon = new Polygon(Polygon.rect(0, nHeight, nWidth, 100));
  3.     var myBody:Body = new Body(BodyType.STATIC);
  4.     myBody.shapes.add(myPolygon);
  5.     myBody.space = mySpace;
  6.   }    
  7. }

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

でき上がったStarlingルートクラス(MySprite)の定義は、つぎのスクリプト003のとおりです。[ムービープレビュー]を確かめると、落下したボールはステージ下端を床にして跳ね返り、バウンドを繰返します。

スクリプト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.BodyList;
  10.   import nape.phys.Material;
  11.   import nape.geom.Vec2;
  12.   import nape.shape.Circle;
  13.   import nape.shape.Polygon;
  14.   import nape.space.Space;
  15.   public class MySprite extends Sprite {
  16.     private var nWidth:int;
  17.     private var nHeight:int;
  18.     private var nRadius:Number;
  19.     private var myBitmapData:BitmapData;
  20.     private var myTexture:Texture;
  21.     private var mySpace:Space;
  22.     public function MySprite() {
  23.       addEventListener(Event.ADDED_TO_STAGE, initialize);
  24.     }
  25.     private function initialize(eventObject:Event):void {
  26.       nWidth = stage.stageWidth;
  27.       nHeight = stage.stageHeight;
  28.       myBitmapData = new Pen();
  29.       myTexture = Texture.fromBitmapData(myBitmapData);
  30.       nRadius = myBitmapData.width / 2;
  31.       mySpace = new Space(new Vec2(0, 2000));
  32.       setBall(mySpace);
  33.       setFloor(mySpace);
  34.       addEventListener(Event.ENTER_FRAME, refreshScreen);
  35.     }
  36.     private function refreshScreen(eventObject:Event):void {
  37.       mySpace.step(1/24);
  38.       var bodies:BodyList = mySpace.liveBodies;
  39.       var nBodies:int = bodies.length;
  40.       for (var i:int = 0; i < nBodies; i++) {
  41.         var myBody:Body = bodies.at(i);
  42.         var update:Function = myBody.userData.graphicUpdate as Function;
  43.         if (Boolean(update)) {
  44.           update(myBody);
  45.         }
  46.       }
  47.     }
  48.     private function setBall(mySpace:Space):void {
  49.       var myImage:Image = createBall();
  50.       addChild(myImage);
  51.       addPhysics(myImage, mySpace, nWidth / 2);
  52.     }
  53.     private function createBall():Image {
  54.       var myImage:Image = new Image(myTexture);
  55.       myImage.pivotX = nRadius;
  56.       myImage.pivotY = nRadius;
  57.       return myImage;
  58.     }
  59.     private function addPhysics(myImage:Image, mySpace:Space, nX:Number = 0, nY:Number = 0) {
  60.       var myBody:Body = new Body(BodyType.DYNAMIC, new Vec2(nX, nY));
  61.       myBody.shapes.add(new Circle(nRadius, null, new Material(20)));
  62.       myBody.userData.graphic = myImage;
  63.       myBody.space = mySpace;
  64.       myBody.userData.graphicUpdate = updateGraphic;
  65.     }
  66.     private function updateGraphic(myBody:Body):void {
  67.       var myImage:Image = myBody.userData.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.       var myBody:Body = new Body(BodyType.STATIC);
  76.       myBody.shapes.add(myPolygon);
  77.       myBody.space = mySpace;
  78.     }    
  79.   }
  80. }

07 落下する剛体を増やす

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

  1.   import flash.utils.Timer;
  2.   import flash.events.TimerEvent;
  1.   public class MySprite extends Sprite {
  1.     private var myTimer:Timer = new Timer(200, 100);
  1.     private function initialize(eventObject:Event):void {
          // setBall(mySpace);
  1.       setFloor(mySpace);
  1.       myTimer.addEventListener(TimerEvent.TIMER, addBall);
  2.       myTimer.start();
  3.     }
  4.     function addBall(eventObject:TimerEvent):void {
  5.       setBall(mySpace);
  6.     }
  1.     private function setBall(mySpace:Space):void {
  2.       var myImage:Image = createBall();
          // addPhysics(myImage, mySpace, nWidth / 2);
  1.       addPhysics(myImage, mySpace, nWidth * Math.random());
  2.     }

Timerオブジェクトのリスナー関数(addBall())を用いて、0.2秒間隔でランダムな水平位置からボールが計100個落とされます(図005)。できあがったStarlingルートクラス(MySprite)の定義は、スクリプト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.BodyList;
  12.   import nape.phys.Material;
  13.   import nape.geom.Vec2;
  14.   import nape.shape.Circle;
  15.   import nape.shape.Polygon;
  16.   import nape.space.Space;
  17.   public class MySprite extends Sprite {
  18.     private var nWidth:int;
  19.     private var nHeight:int;
  20.     private var nRadius:Number;
  21.     private var myBitmapData:BitmapData;
  22.     private var myTexture:Texture;
  23.     private var mySpace:Space;
  24.     private var myTimer:Timer = new Timer(200, 100);
  25.     public function MySprite() {
  26.       addEventListener(Event.ADDED_TO_STAGE, initialize);
  27.     }
  28.     private function initialize(eventObject:Event):void {
  29.       nWidth = stage.stageWidth;
  30.       nHeight = stage.stageHeight;
  31.       myBitmapData = new Pen();
  32.       myTexture = Texture.fromBitmapData(myBitmapData);
  33.       nRadius = myBitmapData.width / 2;
  34.       mySpace = new Space(new Vec2(0, 2000));
  35.       setFloor(mySpace);
  36.       addEventListener(Event.ENTER_FRAME, refreshScreen);
  37.       myTimer.addEventListener(TimerEvent.TIMER, addBall);
  38.       myTimer.start();
  39.     }
  40.     function addBall(eventObject:TimerEvent):void {
  41.       setBall(mySpace);
  42.     }
  43.     private function refreshScreen(eventObject:Event):void {
  44.       mySpace.step(1/24);
  45.       var bodies:BodyList = mySpace.liveBodies;
  46.       var nBodies:int = bodies.length;
  47.       for (var i:int = 0; i < nBodies; i++) {
  48.         var myBody:Body = bodies.at(i);
  49.         var update:Function = myBody.userData.graphicUpdate as Function;
  50.         if (Boolean(update)) {
  51.           update(myBody);
  52.         }
  53.       }
  54.     }
  55.     private function setBall(mySpace:Space):void {
  56.       var myImage:Image = createBall();
  57.       addChild(myImage);
  58.       addPhysics(myImage, mySpace, nWidth * Math.random());
  59.     }
  60.     private function createBall():Image {
  61.       var myImage:Image = new Image(myTexture);
  62.       myImage.pivotX = nRadius;
  63.       myImage.pivotY = nRadius;
  64.       return myImage;
  65.     }
  66.     private function addPhysics(myImage:Image, mySpace:Space, nX:Number = 0, nY:Number = 0) {
  67.       var myBody:Body = new Body(BodyType.DYNAMIC, new Vec2(nX, nY));
  68.       myBody.shapes.add(new Circle(nRadius, null, new Material(20)));
  69.       myBody.userData.graphic = myImage;
  70.       myBody.space = mySpace;
  71.       myBody.userData.graphicUpdate = updateGraphic;
  72.     }
  73.     private function updateGraphic(myBody:Body):void {
  74.       var myImage:Image = myBody.userData.graphic as Image;
  75.       var myPosition:Vec2 = myBody.position;
  76.       myImage.x = myPosition.x;
  77.       myImage.y = myPosition.y;
  78.       myImage.rotation = myBody.rotation;
  79.     }
  80.     private function setFloor(mySpace:Space):void {
  81.       var myPolygon:Polygon = new Polygon(Polygon.rect(0, nHeight, nWidth, 100));
  82.       var myBody:Body = new Body(BodyType.STATIC);
  83.       myBody.shapes.add(myPolygon);
  84.       myBody.space = mySpace;
  85.     }    
  86.   }
  87. }

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

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

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


作成者: 野中文雄
作成日: 2012年4月2日


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