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);



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