2009年8月15日土曜日

DisplayObjectのプロパティ x と y

DisplayObjectのプロパティxyは、座標計算には使えないという話である。なぜなら、その値が小数点以下2桁目のところで四捨五入されてしまうからである。これらのプロパティがNumber型の変数であるにもかかわらずである。型がNumberであることが、このことに気づくのに手間取った理由でもある。

精度を必要としない場合は、もちろん問題ない。精度を必要とする場合、計算にDisplayObjectのプロパティxyを含ませると、精度が悪くなり、思わぬトラブルに巻き込まれる。

まずは、このコードから。

var pt:Point = new Point();
pt.x = Math.PI;
trace("pt.x=",pt.x);


デバッグ用のコンソールに出力される結果は、予想通り

pt.x= 3.141592653589793


となる。16桁の円周率である。次に、このコード。

var shape:Shape = new Shape();
shape.x = Math.PI;
trace("shape.x=",shape.x);


今度は、Shapeオブジェクトのxプロパティ(つまり、DisplayObjectxプロパティ)に同じ円周率を代入している。これの出力結果は、予想に反して、

shape.x= 3.1


となる。なんと、2桁である。表示上の問題も疑えるので(つまり、内部的には切捨てが起こっていないことを期待して)、念のため以下のようにして差を出力して確認してみる。

trace("difference=", pt.x - shape.x);


残念ながら、以下のとおり、内部的にも切捨てが起こっているようである。

difference= 0.04159265358979303



先述のとおり、精度が必要ないのであれば、問題はない。高々プラスマイナス0.05程度の誤差である。ディスプレイ上、1ピクセルの20分1程度の大きさである。そんなずれは、見てわかるものではないだろう。

では、どういうときに問題になるのか?表示上のオブジェクトの座標計算をする際、現在位置を元に次の位置を計算することがある。このようなときに起こる。なぜなら、現在位置が想定される値よりも、最大プラスマイナス0.05程度ずれるからである。

仮に、24FPSのアプリケーションを開発したとして、1ファレーム毎に、座標計算をしたとしよう。すると、先述の円周率の実験で得られた誤差(すなわち、0.04159265358979303)が毎フレームごとに出るとすると、約1秒で、誤差はおよそ1ピクセルに達する。1分で約60ピクセルに達する。こうなれば、どんなに鈍い人でも、そのずれに違和感を感じるだろう。(実際には、誤差はプラスだったりマイナスだったりする可能性があるので、これよりも小さくなることが期待されるが…。)

数字を羅列されてもピンと来ないかもしれない。百聞は一見にしかずかもしれない。以下サンプルのアプリケーションである。




等速円運動を計算したものである。等速円運動ならこうする必要はないが、あえて現在位置を元に次の位置を計算するようにプログラミングしてある。赤いオブジェクト(_red)は、オブジェクト自身の座標(つまりDispalyObjectx,yプロパティ)を元に次の位置を算出してる。緑のオブジェクト(_green)は、別途Point型の変数(_greenPt)を用意し、それを座標計算に用い、計算結果のみをオブジェクト自身の座標に代入している。どちらも一定のスピードで円運動をするはずである。また、どちらも同じ場所からスタートさせている。

var p:Point = getDirection();

_red.x += p.x;
_red.y += p.y;

_greenPt.x += p.x;
_greenPt.y += p.y;
_green.x = _greenPt.x;
_green.y = _greenPt.y;


ご覧のとおり、赤いオブジェクトは徐々に左上にずれていっている。

これは、オブジェクト同士の衝突判定の結果にも影響する可能性がある。キャラクターが壁や地面にめり込んだりする原因になりかねない。こういう場合、DisplayObject(MovieClipSpriteShapeBitmapなど)のx,yプロパティを計算に使うのは賢明ではないだろう。

2009年7月4日土曜日

モーツァルトを弾いてみた。

Flex Builder 3で、Flash Player 10の新機能ダイナミックサウンドを利用してみた。Flex Builder 3で、Flash Player 10の機能を利用する方法は、前回の投稿を参考にしてもらいたい。

20年以上前のパソコンについていた、演奏機能のようなものが出来上がった。コードは、次のとおり。

package {
   import flash.events.Event;
   import flash.events.EventDispatcher;
   import flash.events.SampleDataEvent;
   import flash.events.TimerEvent;
   import flash.media.Sound;
   import flash.media.SoundChannel;
   import flash.utils.Timer;

   public class Sampling extends EventDispatcher {
     private const FREQ:Number = 261.63;
     private const BASE_RATE:Number = 44100;
     private var _rate:Number = 44100;
     private var _noise:Number = 0;

     private var _tones:Object = new Object();
     private var _timer:Timer;

     private var _rateArray:Array = new Array ();

     private var _sound:Sound = new Sound();
     private var _soundChannelObject:SoundChannel = new SoundChannel ();

     public function Sampling() {
      _tones["R"] = 0;
      _tones["C"] = 261.63/220/2;
      _tones["D"] = 293.67/220/2;
      _tones["E"] = 329.63/220/2;
      _tones["F"] = 349.23/220/2;
      _tones["G"] = 392.00/220/2;
      _tones["A"] = 1;
      _tones["B"] = 233.08/220;
      _tones["H"] = 246.94/220;
      _timer = new Timer (80,0)
      _sound.addEventListener (SampleDataEvent.SAMPLE_DATA, samplingHandler);
    }

     private function parse(notes:String):void {
       var note:String;
       var l:int;
       var i:int;
       var arr:Array = notes.split(/(\d+)/);
       while(arr.length > 0) {
         note = arr.shift();
         l = 32/parseInt(arr.shift())-1;

         for(i=0;i < l;i++) {
           _rateArray.push(_tones[note]/BASE_RATE);
        }
        _rateArray.push(0);
      }
    }

     public function play(score:String):void {
      parse(score);
      _timer.addEventListener (TimerEvent.TIMER, timerHandler);
    _timer.start ();
    _soundChannelObject = _sound.play();
    }

     private function timerHandler(e:TimerEvent):void {
       if (!_rateArray.length) {
        _timer.stop ();
        _timer.removeEventListener(TimerEvent.TIMER, timerHandler);
        _soundChannelObject.stop ();
        dispatchEvent (new Event(Event.COMPLETE));
      }
      _rate = _rateArray.shift();
    }

     private function samplingHandler(e:SampleDataEvent):void {
       var samples:Number;
       var s:Number;
       for(var i:int=0; i<2048; i++){
        _noise += (FREQ) * _rate;
        samples = _noise * Math.PI * 2;
        s = (Math.cos(samples) + Math.cos(samples*2) + Math.cos(samples*4))/3;
        e.data.writeFloat(s);
        e.data.writeFloat(s);
      }
    }
  }
}


このクラスの使い方は、こんな感じになる。

var sampling:Sampling = new Sampling();
sampling.play("C4C4G4G4A4A4G2F4F4E4E4D4D4C2G4G4F4F4E4E4D2G4G4F4F4E4E4D2C4C4G4G4A4A4G2F4F4E4E4D4D4C2");



以上は、このページを参考にさせてもらった。

2009年6月27日土曜日

Flash Player 10の機能を使ってみる。

Adobeは、Flex Builderを改め、Flash Builderと呼ぶことにしたようだ。新バージョンを購入する予定は、当面ないので、手持ちの、Flex Builder 3でFlash Player 10の機能を使う方法を探索してみた。以下は、英語版のFlex Builderで確認したもので、日本語版を利用している場合には、必要に応じてメニューなどを日本語に読み替えてもらいたい。


  1. インストールされているFlex SDKを確認する。メニューの "windows/preferences..." を開き、 "Flex Installed Flex SDKs" を見てみる。たとえば、 Flex 3.3 を持っていれば、 Flash Player 10 の機能を使うプログラムをすぐにでも開発できる。持っていないようならば、最新の Flex SDK をダウンロードして、 Flex Builder に登録する。

  2. プロジェクトのコンパイラーの設定を確認する。任意のプロジェクトフォルダー上で、右クリックし、 "properties" を実行する。 "ActionScript Compiler" をクリックし、 Flex SDK のバージョンが、 Flash Player 10 を含んでいるものであることを確認する。たとえば、 Flex 3.3 ならば Flash Player 10 を含んでいる。

  3. 要求される "Flash Player version" を変えておく。上記ダイアログ内の、 "Require Flash Player version" を変更しておけばよい。 Flash Player 10 以上が必要だからだ。設定完了まで、後一歩だ。

  4. Flash Player 10のライブラリを登録する。上記ダイアログ内で、 "ActionScript Builder Path" をクリックし、 "Library path" タブを選択する。ここに、 "playerglobal.swc" を登録する。たとえば、Flex 3.3のSDKならば、<Flex SDK 3.3のフォルダ>\frameworks\libs\player\10にそれが見つかるはずだ。


以上の設定変更で、そのプロジェクトでFlash Player 10の機能を含んむアプリケーションの開発ができるはずだ。

2009年5月2日土曜日

本当に最適化?

計算を高速に行う方法を調べていると、ビットシフトを行う方法をウェブ上でよく見かける。当初は、それを鵜呑みにして利用していたが、どうも、違うのではないかという気がしてきたので、自分でも調べてみた。

まず、よく見かける高速化の方法とは、次のようなものである。


x = x >> 1;


これが「2で割る」という処理の高速版として紹介されている。「計算速度が2倍になった」とか「これをやらない手はない!300%の高速化!」みたいなことが書いてある(主に英語のサイトなので、TORITORIが勝手に意訳しています)。仮にこれが本当だとしても、注意すべきことがある。

  • xは整数でなければならない。整数以外でも動作をするが、計算結果は整数になる。

  • 負の数の場合、結果が異なる。var a:int = -11 / 2では、a=-5となる。一方、var b:int = -11 >> 1では、b=-6となる。

  • 演算の優先順位が異なる。割る(/)は加法・減法よりも優先される。しかし、この代替演算は、加法・減法よりも優先順位が低い。上記二点を理解した上で、単純に割る2の記号(/2)をビットシフトの演算(>>1)に置き換えても、だめである。括弧でくくる必要がある。例えば16 / 2 + 1 と 1 + 16 / 2はどちらも答えは9である。これの/2を単純に>>1に置き換えると、前者は4に後者は8になる。


ややこしいことを書いたが、これだけ注意すべきことがあるということは、書き換えるにはかなり注意が要るということを意味する。単純に機械的な置き換えができないからだ。それでも、2倍3倍に高速化されるなら、やるだけの価値があるかもしれない。

そこで、演算時間を実際に計ってみた。まず、計測に用いたソースコード、次に結果のグラフを載せている。


package {
  import flash.display.Sprite;
  import flash.utils.getTimer;

  public class BitShiftApp extends Sprite {
    private var _max:int = 1000000;
    public function BitShiftApp() {
      var value:int = -11; //Try

      trace(value / 2, value * .5, value >> 1);
      var result1:int;
      var result2:int;
      var result3:int;
      trace("division,multiplication,bit_shift");
      for(var i:int = 0;i < result1 =" division(value)" result2 =" multiplication(value);" result3 =" bitshift(value);" color="#cc6666">        trace(result1,",", result2,",", result3);
      }
    }

    private function division(value:int):int {
      var now:int = getTimer();
      var result:Number;
      for(var i:int = 0;i < result =" value/2;" color="#0033FF">      return getTimer() - now;
    }

    private function multiplication(value:int):int {
      var now:int = getTimer();
      var result:Number;
      for(var i:int = 0;i < result =" value*.5;" color="#0033FF">      return getTimer() - now;
    }

    private function bitshift(value:int):int {
      var now:int = getTimer();
      var result:Number;
      for(var i:int = 0;i < result =" value">> 1;
      }
      return getTimer() - now;
    }
  }
}





「割る2(/2)」と「かける0.5(*.5)」と「右にシフト(>>1)」で比べたものである。それぞれ、10万回の計算にかかる時間である。それぞれ40回行っての平均を比べている。40回のうちの、最初の1回の結果は、計測に用いてない。リソースの利用状況が不安定な場合があるからだ。結果は、ご覧のように、2倍、3倍の差はないのである。

では、なぜ、2倍3倍になったなどという結果が報告されているのだろう?想像するに、1回しか計測していないと思われる。そして、1つのプログラムの中で、最初に通常の割り算、次にビットシフトの順で計算していると思われる。このことから、最初に処理される通常の割り算は、プログラムの起動処理と重なり、処理に時間がかかったように常に見えるのではないだろうか?

これは、以下のような2つのプログラムで簡単に実験できる。最初行われた計算の方が常に遅い。こういったプログラムでは、最初に普通の書き方、次に特殊な書き方で、速度を調べたくなるのが人情なのかもしれない。それぞれ実行してみれば、常に、最初の計算方法の方が遅いという結果が出る。

最初に割り算、次にビットシフトの演算速度を検証しようとするプログラム例


package {
  import flash.display.Sprite;
  import flash.utils.getTimer;

  public class FirstDivNextBit extends Sprite {
    public function FirstDivNextBit() {
      var max:int = 1000000;
      var i:int;
      var value:int = 10;
      var half:int;
      var now:int;

      now = getTimer();
      for(i = 0;i < max;i++) {
        half = value / 2;
      }
      trace(getTimer() - now);

      now = getTimer();
      for(i = 0;i < max;i++) {
        half = value >> 1;
      }
      trace(getTimer() - now);
    }
  }
}


最初にビットシフト、次に割り算の演算速度を検証しようとするプログラム例



package {
  import flash.display.Sprite;
  import flash.utils.getTimer;

  public class FirstBitNextDiv extends Sprite {
    public function FirstBitNextDiv() {
      var max:int = 1000000;
      var i:int;
      var value:int = 10;
      var half:int;
      var now:int;

      now = getTimer();
      for(i = 0;i < max;i++) {
        half = value >> 1;
      }
      trace(getTimer() - now);

      now = getTimer();
      for(i = 0;i < max;i++) {
        half = value / 2;
      }
      trace(getTimer() - now);
    }
  }
}

2009年4月25日土曜日

イージング(easing) その2

今度は、ゆっくりスタートしてゆっくりと止まる例。一端だけゆっくりとさせる場合は、半周期分だけsin関数つかった。この場合は、1周期分使う。グラフにするとこんな感じ。

前回同様、青い線が等速直線運動での経過時間(フレーム数)に対する移動距離を示している。これに重ね合わせるsin関数が赤い線で書かれている。重ねあわされた結果が、緑の線。接線の傾きが速度を意味しているので、起点と終点で傾きがほぼ水平(=ゼロ)であることからも、ゆっくりとスタートし、ゆっくりと止まることが読み取れるだろう。

これを実装したのが、以下のコード。前の投稿で紹介した親関数の派生クラスである。

package com.toritoriworks.utils {
  public class BothEndsEasing extends Easing {
    public function BothEndsEasing(start:Number, end:Number, frame:int, easing:Number) {
      super(start, end, frame);
      var e:Number = easing <> 1) ? 1 : easing);
      var w:Number = 2 * Math.PI / (frame-1);
      var a:Number = e * _vx / Math.sin(w);
      for(var i:int = 0;i < frame;i++) {
        _Asin.push(a * Math.sin(w * i));
      }
    }
  }
}


最初の引数が、起点の座標、第2引数が終点の座標、第3引数が終点に至るまでのフレーム数、最後の引数が、イージングの強さになる。イージングの強さは、0以上1以下の数値で指定する。0ならば、イージング効果なしとなる。それ以外の値を入れた場合、0から1近い方の値に読み替えられる。次のコードは、起点0、終点100で、終点まで24フレームで移動する。最後の引数が1なので、この実装でのイージング効果は最大になっている。


var myEasing = new BothEndsEasing(0,100,24,1);

addEventListener(EVENT.ENTER_FRAME, frameHandler);

function frameHandler(event:Event):void {
  myMovieClip.x = myEasing.forward();
}



動作は、こんな感じになる。




2009年4月18日土曜日

イージング(easing)

画面上のオブジェクトが徐々に動き出したり、徐々に遅くなって、止まったり効果をイージングと呼ぶ。これを実装してみた。物理を使って、加速度やら、速度やらを時間とともに調整して行うことも可能だが、今回は、物理なしで行った。

等速運動だった場合の位置を、三角関数でずらす方法で実装してみた。

まず、イージングのための親関数がこれ

package com.toritoriworks.utils {
  public class Easing extends Object {
    protected var _x0:Number;
    protected var _frame:Number;
    protected var _vx:Number;
    protected var _Asin:Array = new Array();
    protected var _pos:int;

    public function Easing(start:Number,end:Number,frame:int) {
      _frame = frame;
      _x0 = start;
      _vx = (end-start) / (frame-1);
      initialize();
    }

    public function initialize():void {
      _pos = 0;
    }

    public function forward():Number {
      var pp:Number;
      if (isMoving()) {
        pp = _x0 + _pos * _vx - _Asin[_pos];
        _pos++;
      } else {
        pp = _x0 + (_frame-1) * _vx - _Asin[_frame-1];
      }
      return pp;
    }

    public function isMoving():Boolean {
      return _pos;
    }
  }
}


これを派生して、以下のようなクラスを作る。


package com.toritoriworks.utils {
  public class OneEndsEasing extends Easing {
    public function OneEndsEasing(start:Number, end:Number, frame:int, easing:Number) {
      super(start,end,frame);
      var e:Number = easing < -1? -1 : ((easing > 1) ? 1 : easing);
      var w:Number = Math.PI / (frame-1);
      var a:Number = e * _vx / Math.sin(w);
      for(var i:int = 0;i < frame;i++) {
        _Asin.push(a * Math.sin(w * i));
      }
    }
  }
}


以下のようにして作成されたインスタンスで、forwardメソッドは、呼ばれるたびに次の座標を返してくれる。


var myEasing = new OneEndsEasing(0,100,24,-1);

addEventListener(EVENT.ENTER_FRAME, frameHandler);

function frameHandler(event:Event):void {
  myMovieClip.x = myEasing.forward();
}


この場合、起点を0、終点を100として、x座標が24フレームの間に、増えていく。徐々に動き出し、終点では、ピタッと止まる。

仕組みは、以下のとおり。等速直線運動(青い線)の動きに、sin関数(赤い線)の動きが足しあわされている。その結果が、緑の線である。グラフは、時間に関する位置のグラフなので、傾きは速さを表している。緑の線は、ほぼ横ばいにスタートしているので、速さはほぼゼロでスタートしていることになる。徐々に加速し、終点で最高速に達し、ピタッと止まるわけだ。sin関数の振幅は、速度が負にならず、効果が最大になるように調整されている。それが、コンストラクタの最後の引数。-1を与えると効果が最大になる。-1以下の値を与えても、-1として動作する。0でイージングの効果がでなくなる。





次のグラフは、起点、終点、およびフレーム数が同じだが、終点付近で徐々に減速し、止まる例。






var myEasing = new OneEndsEasing(0,100,24,1);


インスタンス作成の部分を上記のようにすればよい。1より大きな値を与えても、1として動作する。0でイージングの効果がでなくなる。

親クラスであるEasingクラスは、直接インスタンス化できてしまうが、意味を成さない。そのインスタンスを使用すると、_Asinが空なので、エラーが発生する。これを回避するコードは、このテーマの本質ではないので、割愛した。

2009年4月11日土曜日

正規乱数

次のクラスは、正規乱数を発生させるためのもの。Math.randomのように、一様に乱数が発生するのではなく、正規分布(ガウス分布)にしたがって、発生するというものだ。Box-Muller法で実装してみた。

ある特定の数値付近の値(期待値)が最も高確率で発生するが、場合によってはすごく違う値が発生するのだ。どのくらいの確率でずれるかは、「期待値の半分の確率で発生する値を指定する」ことで調整するようにしてある。

コンストラクタの第1引数が、その期待値を、第2引数が、期待値の半分の確率で発生する値までの距離になる。パブリックな読み取り専用プロパティ、floatとintegerがある。それぞれ、Number型、int型の正規乱数を返す。

package {
  public class Gaussian {
    private static const _2pi:Number = Math.PI;
    private static const _hwhm:Number = 2.35 / 2;
    private var _half:Number = _hwhm;
    private var _peak:Number = 0;

    public function Gaussian(expectation:Number,hwhm:Number) {
      _half = hwhm / _hwhm;
      _peak = expectation;
    }

    private function normalDistribution():Number {
      var u1:Number = Math.random();
      var u2:Number = Math.random();

      return _half * Math.sqrt(-2 * Math.log(u1)) * Math.cos(_2pi * u2);
    }

    public function get float():Number {
      return normalDistribution() + _peak;
    }

    public function get integer():int {
      var g:Number = normalDistribution();
      return int(g+((g > 0)? .5 : -.5))+_peak;
    }
  }
}


たとえば、次のように利用する。


var generator1:Gaussian = new Gaussian(10,2);
var a:int = generator1.integer;

var generator2:Gaussian = new Gaussian(10,10);
var b:int = generator2.integer;



上記例では、変数aは、10になる確率が最も高い。ただし、その半分ほどの確率で、8(=10-2)あるいは12(=10+2)になるのである(赤い棒グラフ)。変数bも、10になる確率が最も高い。半分ほどの確率で、0(=10-10)または20(=10+10)になる(青い棒グラフ)。それゆえに、aが10になる確率はbが10になる確率よりも大きい。そして、aもbも10から大きく外れた値になる確率は限りなくゼロに近いが、ゼロではない。

2009年4月4日土曜日

乱数

ご存知のとおり、ActionScriptでは、Math.randomで乱数を発生させることができる。これは、0以上1未満の数値が同じ確率で発生する擬似乱数である。以下のようなクラスを用意しておくと、意外と重宝する。

package {
  public class Random {
    /**
      * @param inc included number
      * @param exc excluded number
      * @return random number between inc and exc
      */

    public static function between(inc:Number, exc:Number):Number {
       return Math.random() * (exc - inc) + inc;
    }

    /**
      * @return -1 or 1.
      */

    public static function sign():Number {
       return (Math.random() < .5)? -1 : 1;
    }
  }
}



使い方は、こんな感じになる。

  • aは10以上20未満の実数値。


    var a:Number = Random.between(10,20);


  • aは1または-1。


    var a:Number = Random.sign();


  • aは10以上20未満または、-20より大きく-10以下の実数値。


    var a:Number = Random.sign() * Random.between(10,20);



いずれの場合も、範囲内の数値が同確率で発生するところがポイント。次回は、正規乱数について。

2009年3月28日土曜日

Verlet法

今回の投稿は、ActionScriptとは直接関係ない話。プログラム上で物体の動きを再現する上で避けて通れないのが、物理法則。そして、それらを計算するための数学…とくに積分。
ある瞬間の位置をr0、次の瞬間の位置をr1として、さらに次の瞬間の位置をr2し、r1からr2へ移動する間の加速度の和をatとすると、
r2= 2 * r1 - r0 + at
と書き表せるらしい。これをVerlet法(Verlet積分法)と呼ぶそうだ。

Euler法と比べると、精度もよく、計算量も少ないので、ある条件下では非常に便利である。その条件とは?プログラム上速さを制御する必要がない場合である。計算量が少なくてすむのは、ご覧のとおり、いちいち、速さを介在させて位置を計算しないからである。逆にそれが、仇となり、初速度v0で動かしたいとか、空気抵抗が速さに比例するなどとなってくると、少々面倒だ。

ただ、そういう場合でも、正確さがあまり必要でない場合、ある程度ごまかせるので、やはり便利だ。

2009年3月21日土曜日

ENTER FRAME イベント その4

前回は、ENTER_FRAMEイベントの登録、開放を意識することなく、ENTER_FRAMEイベントを利用するシンプルな方法を示した。しかし、実際には、その処理をもう少し制御できないと不便なことが多い。そこで、今回は、このライブラリを使って処理を制御する方法を示す。FrameHelperを使ってセットアップしたENTER_FRAMEイベントの処理は、次のようにして、そのループを一時停止したり、再開したりできる。setupメソッドの戻り値として得たヘルパークラスのインスタンスのメソッドを呼ぶことで実現できる。

package {
  import com.toritoriworks.helpers.FrameHelper;
  import com.toritoriworks.helpers.IHelper;

  import flash.display.Sprite;
  import flash.events.Event;
  import flash.events.MouseEvent;

  [SWF(width="240",height="180",frameRate="24",backgroundColor="0x336699")]
  public class EnterFrame3 extends Sprite  {
    private var _sprite:Sprite;
    private var _helper:IHelper ;
    public function EnterFrame3() {
      _sprite = new Sprite();
      _sprite.graphics.beginFill(0x339900);
      _sprite.graphics.drawRoundRect(0,0,50,50,5);
      _sprite.graphics.endFill();
      _sprite.y = 100;
      addChild(_sprite);

      _helper = FrameHelper.setup(_sprite,null,mainLoop,null);
      _sprite.addEventListener(MouseEvent.CLICK, pauseHandler);
    }

    private function pauseHandler(event:Event):void {
      _sprite.removeEventListener(MouseEvent.CLICK, pauseHandler);
      _helper.pause();
      _sprite.addEventListener(MouseEvent.CLICK, resumeHandler);
    }

    private function resumeHandler(event:Event):void {
      _sprite.removeEventListener(MouseEvent.CLICK, resumeHandler);
      _helper.resume();
      _sprite.addEventListener(MouseEvent.CLICK, pauseHandler);
    }

    private function mainLoop():Boolean {
      _sprite.x += 5;
      if (_sprite.x> 240) {
        _sprite.x = 0;
      }
      return true;
    }
  }
}


実行結果は、次のとおりである。矩形をクリックすると、それが止まったり右へ動いたりを交互に繰り返す。



ここでは、pause()、resume()の例を示したが、他にrevert()、skip()、cancel()がある。pause(),resume(),revert(),skip()の動作については、「ENTER FRAME イベント その2」のチャートを見てもらいたい。cancel()については、複雑になるので、あえてチャート上に表示しなかった。

cancel()メソッドは、その場で、FrameHelperの処理をすべて中断し、setupメソッドで与えられた関数を実行することなく、処理を終了する。

2009年3月15日日曜日

ENTER FRAME イベント その3

前回、予告したクラスがFrameHelperというクラスである。これを用いると、ENTER_FRAMEイベントを意識することなく、そのイベントを利用できる。開発者をaddEventListener、removeEventListenerの呪縛から開放してくれる(もちろん、メモリリークのリスクがゼロになるわけではない)。「ENTER FRAME イベント その1」で紹介したコードは、次のように書き換えられる。

package {
  import com.toritoriworks.helpers.FrameHelper;

  import flash.display.Shape;
  import flash.display.Sprite;

  [SWF(width="240",height="180",frameRate="24",backgroundColor="0x336699")]
  public class EnterFrame extends Sprite {
    private var _shape:Shape;
    public function EnterFrame() {
      _shape = new Shape();
      _shape.graphics.beginFill(0x339900);
      _shape.graphics.drawRoundRect(0,0,10,10,5);
      _shape.graphics.endFill();
      _shape.y = 100;
      addChild(_shape);

      FrameHelper.setup(_shape,null,mainLoop,null);
    }

    private function mainLoop():Boolean {
        _shape.x += 5;
        if (_shape.x > 240) {
          _shape.x = 0;
        }
        return true;
    }
  }
}



ここで、mainLoop()関数がfalseで終了するようにすると、ENTER_FRAMEのイベントリスナーは勝手に解放されるようになっている。それを利用した実装例を次に示す。矩形がズームインしてくる例である。



package {

  import com.toritoriworks.helpers.FrameHelper;

  import flash.display.Shape;
  import flash.display.Sprite;

  [SWF(width="240",height="180",frameRate="24",backgroundColor="0x336699")]
  public class EnterFrame2 extends Sprite {
    private var _shape:Shape;
    public function EnterFrame2() {
      _shape = new Shape();
      _shape.graphics.beginFill(0x339900);
      _shape.graphics.drawRoundRect(-40,-40,80,80,5);
      _shape.graphics.endFill();
      addChild(_shape);
      _shape.x = 100;
      _shape.y = 100;

      FrameHelper.setup(_shape,begin,mainLoop,finish);
    }

    private function begin():void {
      _shape.scaleX = .1;
      _shape.scaleY = .1;
    }

    private function mainLoop():Boolean {
      _shape.scaleX += (1-_shape.scaleX) * .1;
      _shape.scaleY += (1-_shape.scaleY) * .1;
      return _shape.scaleX < .98;
    }

    private
function finish():void {
      _shape.scaleX = 1;
      _shape.scaleY = 1;
    }
  }
}


ご覧のとおり、ENTER_FRAMEのイベントの登録・開放を意識することなく実装できる。これは、開発者への負荷をかなり軽減するものと信じる。

上記二例では、単にENTER_FRAMEイベント内の処理が終わるのをただ待つだけである。しかし、「ENTER_FRAME イベント その2」のチャートで示したとおり、処理の流れを制御することもできる。次回ではその例を示したいと思う。

FrameHelperクラスのダウンロードは、こちらから。

2009年1月4日日曜日

ENTER FRAME イベント その2

さて、ENTER_FRAMEイベントで何か処理をするとき、大抵ループの処理になるのだが、概ね次のような処理が一般的に必要になる。事前にそれらの処理が実装されていれば、後で必要になっても、コードがスパゲティーにならずに済み、イベントリスナーの消し忘れや、二重登録等によるバグにリソース不足に悩まされることも減るだろう。そんなクラスを実装してみたいと思う。

アプリケーション固有に実装すべき処理は、以下の3つになるはず。

  • ENTER_FRAMEイベントを受けて処理される、ループ内処理の本体

  • ループ開始前で処理される、前処理

  • ループ処理完了後に処理される、後処理


ただし、常に3つすべて必要になるわけではないので、省略可能とする。これらの処理を制御するためのメソッドは次の5つ。

  • start()...ENTER_FRAMEイベントリスナーを登録し、ループ処理を開始する。このメソッドを実行する前に、前処理は実行されている。

  • pause()...ループ処理を一時停止する。実際には、ENTER_FRAMEのイベントリスナーの登録を解除する。再度イベントリスナーの処理を開始したければ、resume()を呼ぶ。

  • resume()...pause()により一時停止されたループ処理を、再開する。

  • revert()...ENTER_FRAMEのイベントリスナーの登録を解除して、前処理を再度実行したところで止まる。再度、イベントリスナーを登録したければ、start()を呼ぶ。

  • skip()...ループ処理の完了を待たずに、ループを抜け(すなわち、イベントリスナーの登録を解除し)、直ちに、後処理を実行する。

  • 各状況において、適切でないメソッドを呼んだ場合、何も実行されない。たとえば、pause()を実行した後に、start()を実行しても何も実行されない。


言葉で書くとややこしいが、絵にしてみるとこんなフローを制御できるクラスになるはず。…といいながら、このアニメーションもすでに、このクラスで実装されているのだが…。


このクラスを使った実装例を次回に紹介したい。