2011年4月9日土曜日

Arrayのイテレーション

Arrayの中身を1つ1つ見ながら何か処理をする事がよくある。ActionScriptでは、その実装方法としていくつかのやり方が提供されている。以前から、どの方法が速いのか気になっていたので、それを調べてみた。

大きく4つの方法が考えられる。

方法1:最も古典的(?)な方法

    for (var i:int = 0;i < arr.length;i++)


方法2:arr.lengthをfor文の外に出した方法。

    var length:int = arr.length;
    for (var i:int = 0;i < arr.length;i++)


方法3:for each文を使った方法

    for each(var i:int in arr)


方法4:Array.forEachを使った方法

    arr.forEach(...);


それぞれの処理スピードを比較してみた。方法1が一番遅いので、それと比較してどの程度速いかを以下に示す。



方法2:18%速い
方法3:15%速い
方法4:33%速い


考察
ActionScriptでは、Array.lengthプロパティが遅い事はよく知られている。なので、これをfor文の中で利用するとループが極端に遅くなる。これが、方法1が遅い理由だ。なぜ、Array.lengthが遅いのかはわからない。方法2が存在する理由もここにある。方法3は方法2より速いと思われたが、結果は予想に反していた。方法3の方が、可読性に優れている気もするが、スピードを気にするなら方法2や方法4を選んだ方がいいようだ。方法4は予想通り最も速かった。

イテレーションをする場合、単にスピードだけでなく、その中で何をするかによっても、選ばれる方法は変わってくる。今後はこれを参考に方法を選びたいと思う。

2010年10月2日土曜日

その点は囲まれたか?

フリーハンドで描かれた線で囲まれた中に、ある点が含まれるかどうか?それをチェックする方法を実装してみた。

いろいろ調べてみたところ、その点から360度を眺めてみたとき、どの方向にも線が見えたら「囲まれている」というアイデアに感銘を受けたので、それで実装してみた。ActionScriptだったら、囲まれた範囲を塗りつぶして、その図形と点とが交差するかどうか、hitTestするなんてこともできるが、あえてそれは避けてみた。

線上、頂点上に調べたい点がある場合、その点は含まれてないと判定されるようにしてみた。つまり、接しているのではなく「囲まれていること」とした。ただし、きっちり閉じた図形で囲む必要はないように実装している。

マウスの右ボタンを押しながらマウスを動かすと線が描けるようになっている。そうして描いた線で、虫を囲んでほしい。「囲まれた」と判断されると、他の場所に新たな虫が現れる。


2010年9月25日土曜日

四則演算の怪

今回の話題は、ActionScriptでクラスを定義していたときに出会った不思議な現象。四則演算の結果が、クラスのどこに書かれるかによって結果がかわってくるというもの。今回説明のために用いる計算式は、5÷10×3である。計算結果は、1.5となるはずである。

ところが、ActionScriptでは、この式がどこに書かれるかによって、結果がかわってくるのである。次がそれを見るためのサンプルコードである。

package {
  import flash.display.Sprite;

     public class Arithmetic extends Sprite {

     private var a:Number = 5 / 10 * 3;
     private var b:Number = 5 * 3 / 10;
     private var c:Number = 5.0 * 3.0 / 10.0;

     public function Arithmetic() {
       var d:Number = 5 / 10 * 3;
       var e:Number = 5 * 3 / 10;
       trace("a =", a);
       trace("b =", b);
       trace("c =", c);
       trace("d =", d);
       trace("e =", e);
       trace(”5/10*3 =", 5 / 10 * 3);
       trace("5*3/10 =", 5 * 3 / 10);
    }
  }
}


このコード内にはaからeまでの変数が定義されており、それぞれに先述の計算式の結果が代入されるようになっている。したがって、すべての変数の値は同じ1.5になると予想される。最後の2行のtraceは変数を媒介しない場合の結果を念のため出力している。もちろん、この結果も1.5となるだろう。では、結果である。


a = 0
b = 1
c = 1.5
d = 1.5
e = 1.5
5/10*3 = 1.5
5*3/10 = 1.5


なんと、変数abは、予想外の結果である。どうも整数として計算されているようだ。あえて小数点以下を記述してみると、cにあるように1.5になる。しかし、コンストラクタ内の式では、小数点などつけなくてもちゃんと実数として計算されている。とんだ落とし穴である。メンバー変数の定義に初期値を計算式で記述する場合には、注意が必要なようだ。どうしてこうなるのか理由は不明だ。

ちなみに、a=0となる理由は、最初の割り算の結果が0になっているからで、その後は何をかけ算しようと結果は0である。一方、b=1となる理由は、先にかけ算が実行される事で値が0になる事を防いでいるからである。

2010年9月11日土曜日

モーション あれこれ その5

前回まで、モーションの中で、とくにイージングを扱ってきた。今回は、バウンドしながら止まるモーションを扱ってみたい。ヒントになる関数は、反比例とsin関数を合成したもの。グラフにすると、次のようなイメージ。赤い破線が反比例のグラフで、緑の破線がsin関数。これらを掛け合わせたグラフが青い実線のグラフである。これのx>0の部分を使おうというアイデアである。

ちなみに、x=0のときは、計算不能である。少なくともActionScriptでは。幸いな事に、このときの値を1とすると、グラフが滑らかにつながる事が知られている。x=0のときは、1として計算するようにプログラミングすればよい。

これを、今まで同様、物体の初期座標をp0t秒後の座標をp(t)とする。そして、物体は、t1秒後にp1の位置で停止するものとした。これらの記号を用いて式を書き直すと、となる。ここで、nは物体が到着地で止まらずに行き過ぎる回数である。時間と移動距離のグラフは、のようになる。これは、n=3のときの例で、p1の位置を3度通り過ぎている事がわかる。止まるときにバウンドしながら止まるわけだ。

最初の到着時刻(すなわち、最初にと着地点を通り過ぎる時刻)t0は、t0=t1/(n+1)である。また、最初の到着時刻からt1を求めるには、t1=(n+1)t0となる。

2010年9月4日土曜日

モーション あれこれ その4

前回の続き、n次関数を使って始点終点の両方でイージング効果を得られる式を考察してみる。今まで同様、物体の初期座標をp0t秒後の座標をp(t)とする。そして、物体は、t1秒後にp1の位置で停止するものとする。

式は、今までのように1つの式では書けず、tの値によって次のようになる。
この式は、t=t1/2のところで、ちゃんと滑らかにつながっている。グラフにすると、となる。赤い線がn=2、黄色い線がn=3、緑の線がn=4のときのものである。参考までに、等速運動と単振動を合成した場合のグラフ(青い線)も載せておいた。どうやら、n=2では、等速運動と単振動を合成したものと比べて、強いイージング効果を得る事はできないようだ。n=3以上ならば、より強いイージング効果が得られる。必要に応じて、使い分ければよい。

2010年8月28日土曜日

モーション あれこれ その3

前回の予告通り、n次関数を使っても、イージング効果を付加した式が作れる事を見てみたい。ご覧の通り、この視点では、緩やかに動き出すようにはとても見えない。しかし、xの範囲を0以上1未満に限定してみると、ご覧の通り緩やかに動き出す式を作れそうに見える。次数をあげる程、ゆっくりと動き出すのだ。
これを、物体の初期座標をp0t秒後の座標をp(t)とする。そして、物体は、t1秒後にp1の位置で停止するものとした、記号で式を書き直すと、となる。これをグラフにするととなる。
赤い線がn=2で、黄色い線がn=3のときのものである。参考までに、等速運動と単振動を合成した場合のグラフ(青い線)も載せておいた。ただし、このままで始点でのイージング効果しかえられない。

ひっくり返せば、終点でだけのイージング効果が得られる。その式は、となる。これをグラフにするととなる。
赤い線がn=2で、黄色い線がn=3のときのものである。参考までに、等速運動と単振動を合成した場合のグラフ(青い線)も載せておいた。

では、次回、n次関数を使って両端でイージング効果を得る式を考察してみる。

2010年8月21日土曜日

モーション あれこれ その2

前回は、単振動のイージング効果を出せる数式を見てみた。今回は、以前このブログでも考察した等速運動と単振動をあわせたイージング効果を出せる式を紹介する。

前回と同じ記号、すなわち、物体の初期座標をp0t秒後の座標をp(t)とする。そして、物体は、t1秒後にp1の位置で停止するものとする。

等速運動と単振動をあわせた式は、となる。ここで、単振動の振幅は、始点および終点を超えない範囲で最大のものを選んである。時間と移動距離のグラフは、となる(赤い線)。比較のために、単振動のグラフも載せてある(青い線)。始点と終点でのグラフの傾きから、等速運動と単振動をあわせた今回の式の方が、より大きなイージング効果が出ている事がわかる。

ところで、次のようなn次関数を使ってもイージング効果を得る事ができる。一見すると、位置として扱う縦軸の値が急激に大きくなりすぎるようで、さらに、nが大きな数になれば、とてもゆっくりと動き出すようには思えない。ところが、視点をかえると、うまい具合に使える事がわかる。次回はそれを見てみたい。

2010年8月15日日曜日

モーション あれこれ その1

アクションスクリプトで物体に動きをつけるとき、単純に座標を増やしたり減らしたりでは、味気ないものになりがちである。イージングと呼ばれるゆっくり動き出したり、ゆっくりと止まったりする効果をつけたりする。また、止まるときに、跳ね返るような効果をつける事がある。ここでは、それらに使えそうな数式を考えてみたい。

まずは、記号の説明から。ここでは、物体の初期座標をp0t秒後の座標をp(t)とする。そして、物体は、t1秒後にp1の位置で停止するものとする。ここでは、tを秒数(=時間)としたが、アクションスクリプトでのフレーム数に置き換えてもよい。

最初に、単純に座標を増やす例から。つまり、先に味気ないと言ったものである。これは、等速運動と呼ばれる。先の記号を使って書けば
となる。これを時間と移動距離のグラフにすると、のようになる。線の傾きが速さに示しているグラフである。

さて、イージングの効果が着いた場合、グラフはどうなるのか?始点、終点での線の傾きが水平に近くなるような曲線になると思われる。そのような形のグラフが描ける数式として、真っ先に思い浮かぶのは三角関数である。先の、記号を使って、式を書けば、となる。単振動と呼ばれる運動だ。これを時間と移動距離のグラフにすると、のようになる。両端で線はほぼ水平になっている。つまり、ゆっくりと動き始め、ゆっくりと止まる。しかし、これを実際に使ってイージング効果を付加してみるとわかるが、意外と効果が小さいのである。次回は、他の方法を考察してみる。

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()を実行しても何も実行されない。


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


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