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プロパティを計算に使うのは賢明ではないだろう。