AS3Graphics#5 Pointでシンプル物理演算

Pointクラスを使ったシンプルな物理演算を解説します。

Pointクラス

AS3Graphics#3で解説したPointクラスを改めて取り上げます。読んで字のごとく座標を扱うクラスですが、二次元座標空間におけるベクトルを表現するのに使う事もできます。つまり、速度や加速度をPointクラスを用いて表すことができます。今回は、Pointクラスを使用して、球体が互いにぶつかり合って跳ね返る様子をシミュレートしてみます。

package { import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Point;

/** * 単純な物理演算のサンプルです。 * @author ishigami.shintaro * */ public class PhysicsTest extends Sprite {

/** 描画領域の幅です。 */ public static const WIDTH:int = 800;

/** 描画領域の高さです。 */ public static const HEIGHT:int = 400;

/** * コンストラクタです。 */ public function PhysicsTest() { super(); stage.frameRate = 60; //背景を塗ります。 graphics.beginFill(0); graphics.drawRect(0, 0, WIDTH, HEIGHT); graphics.endFill(); stage.addEventListener(MouseEvent.CLICK, clickHandler); //Sphereを20個生成します。 for (var i:uint = 0; i < 20; i++) { var sphere:Sphere = new Sphere(); sphere.x = Math.random() * WIDTH; sphere.y = Math.random() * HEIGHT; addChild(sphere); } } /** * stageのclickハンドラです。 * @param event */ private function clickHandler(event:MouseEvent):void { //全てのSphereを上方向に加速します。 for each (var s:Sphere in Sphere.instances) { s.velocity.y -= 20; s.velocity.x += Math.random(); } } } } import flash.display.GradientType; import flash.display.Graphics; import flash.display.InterpolationMethod; import flash.display.SpreadMethod; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Matrix; import flash.geom.Point; class Sphere extends Sprite { /** * 全てのSphereインスタンスのVectorです。 */ public static var instances:Vector. = new Vector.();

/** * 弾性係数です。 */ private static const ELASTICITY:Number = 0.9;

/** * 重力加速度(ピクセル/フレーム^2)です。 */ private static const GRAVITY_ACCERALATION:Point = new Point(0, 0.5);

/** * 半径です。 */ private static const RADIUS:int = 18;

/** * 境界反射時に切り捨てられる速さです。 */ private static const BOUNDING_FIX_SPEED:Number = 0.2;

/** * コンストラクタです。 */ public function Sphere() { //球体を描画します。 var g:Graphics = graphics; var rotation:Number = Math.PI / 4 * 5; //回転 var m:Matrix = new Matrix(); m.createGradientBox(RADIUS * 2, RADIUS * 2, rotation, -RADIUS, -RADIUS); var colors:Array = [0xFFFFFF, 0x333333]; //色 var alphas:Array = [1, 1]; //アルファ var ratios:Array = [0x00, 0xFF]; //色分布比率 var focalPointRatio:Number = 0.5; //焦点位置比率 g.beginGradientFill( GradientType.RADIAL, colors, alphas, ratios, m, SpreadMethod.PAD, InterpolationMethod.RGB, focalPointRatio); g.drawCircle(0, 0, RADIUS); g.endFill(); //インスタンス固有の番号を設定します。 index = instances.length; instances.push(this); addEventListener(Event.ENTER_FRAME, enterFrameHandler); }

/** * インスタンス固有の番号です。生成された順を示します。 */ public var index:uint;

/** * 速度です。 */ public var velocity:Point = new Point();

/** * enterFrameハンドラです。 * @param event */ private function enterFrameHandler(event:Event):void { //速度に応じて座標を移動します。 x = x + velocity.x; y = y + velocity.y; //速度を再計算します。 updateVelocity(); }

/** * 速度を求めます。 */ private function updateVelocity():void { //左右の境界で跳ね返る処理です。 if (PhysicsTest.WIDTH - width / 2 < x) { velocity.x *= -ELASTICITY; x = PhysicsTest.WIDTH - width / 2 fixXBound(); } else if (x < width / 2) { velocity.x *= -ELASTICITY; x = width / 2; fixXBound(); } //上下の境界で跳ね返る処理です。 if (PhysicsTest.HEIGHT - height / 2 < y) { velocity.y *= -ELASTICITY; y = PhysicsTest.HEIGHT - height / 2; fixYBound(); } else if (y < width / 2) { velocity.y *= -ELASTICITY; y = width / 2; } //他のインスタンスとの衝突判定処理です。 for (var i:uint = index + 1; i < instances.length; i++) { var target:Sphere = instances[i]; var distance:Point = new Point(target.x - x, target.y - y); //判定対象Sphereとの距離 if (distance.length < RADIUS * 2) { var collisionAngle:Number = Math.atan2(distance.y, distance.x); //衝突角度 var impactAngle1:Number = Math.atan2(velocity.y, velocity.x) - collisionAngle; //自身の反動の角度 var impactAngle2:Number = Math.atan2(target.velocity.y, target.velocity.x) - collisionAngle; //対象の反動の角度 var impactPower1:Number = velocity.length * Math.cos(impactAngle1); //自身の反動加速量 var impactPower2:Number = target.velocity.length * Math.cos(impactAngle2); //対象の反動加速量 var impactPower:Number = (impactPower1 - impactPower2) * ELASTICITY; //反動加速量 var impactAcceralation:Point = Point.polar(impactPower, collisionAngle); //反動加速度 //求めた反動加速度から自身と対象の速度を求めます。 velocity = velocity.subtract(impactAcceralation); target.velocity = target.velocity.add(impactAcceralation); //重ならないように位置を調整します。 distance.normalize((RADIUS * 2 - distance.length) / 2); x -= distance.x; y -= distance.y; target.x += distance.x; target.y += distance.y; } } //重力加速度による加速を行います。 velocity = velocity.add(GRAVITY_ACCERALATION); } /** * 下境界跳ね返り時に一定以下の速さYを切り捨てます。 */ public function fixYBound():void { if (Math.abs(velocity.y) < BOUNDING_FIX_SPEED) { velocity.y = 0; } } /** * 左右境界跳ね返り時に一定以下の速さXを切り捨てます。 */ public function fixXBound():void { if (Math.abs(velocity.x) < BOUNDING_FIX_SPEED) { velocity.x = 0; } } } [/as3]

実行結果(クリックで動きます。)

[SWF]http://public-blog-dev.s3.amazonaws.com/wp-content/uploads/2011/09/PhysicsTest.swf, 800, 400[/SWF]

このサンプルを見るにはFlash Playerがインストールされている必要があります。

解説

enterFrameイベントで、球体同士が接触しているかどうかを球体間の距離でチェックし、もし接触していた場合は、2つの球体の接触した角度とそれぞれの速度と弾性係数から反動の角度と加速量を計算して、跳ね返った後の速度を算出しています。衝突反動の計算が若干複雑ですが、かなり短くまとめることができました。