読者です 読者をやめる 読者になる 読者になる

Murayama blog.

プログラミングと、その次の話

バネのオフセット


天井に吊るしたバネとボールがあるとします。


イメージはこんなかんじ。





で今回習うのはこの|の部分。
つまりオフセット。


バネがビヨンビヨンしても|の距離は保ちましょう、ってサンプルです。

このサンプルでは天井もボールになっていて、互いにバネ運動を及ぼし合う関係になっています。
この辺からコードはちょっと難しくなるかも。


まず、2つのボールを作成した後、

		private function onEnterFrame(evt:Event):void
		{
			// ドラッグしていないときは動いてよし
			if(!ball1Dragging){
				springTo(ball1, ball2)
			}
			if(!ball2Dragging){
				springTo(ball2, ball1)
			}
			
			// 線(バネ)の描画
			graphics.clear();
			graphics.lineStyle(1);
			graphics.moveTo(ball1.x, ball1.y);
			graphics.lineTo(ball2.x, ball2.y);
		}

てかんじで、ドラッグしていないときはボールがバネ運動するようにしています。


実際のバネ運動springToメソッドは、こんなかんじです。

		private function springTo(ballA:Ball, ballB:Ball):void{
			// 対象ballBに対してballAを動かす
			// ballBとballAの距離
			var dx:Number = ballB.x - ballA.x;
			var dy:Number = ballB.y - ballA.y;
			// 縦横の距離から角度を算出
			var angle:Number = Math.atan2(dy, dx);
			// オフセットを加味して実際の"対象"を算出 −にすることで右上になる
			var targetX:Number = ballB.x - Math.cos(angle) * springLength;
			var targetY:Number = ballB.y - Math.sin(angle) * springLength;			
			
			// 速度を設定
			ballA.vx += (targetX - ballA.x) * spring;
			ballA.vy += (targetY - ballA.y) * spring;
			// 摩擦
			ballA.vx *= friction;
			ballA.vy *= friction;
			// 移動
			ballA.x += ballA.vx;
			ballA.y += ballA.vy;
		}


このサンプルでは、ボールが2つ(ball1とball2)あって、
このどちらのボールもバネ運動を受ける対象、バネ運動を与える対象になります。
そのため、片方のボールを距離を算出する対象とし、もう片方のボールを動かすというプログラムになっています。
このメソッドの中では、2番目の引数ballBが距離を算出する対象で、1番目の引数ballAが移動するボールになります。


さて、本題のオフセットの加え方ですが、
このサンプルではspringLengthという変数に100というオフセットが入っています。
つまりボールとボールは100ピクセル保つ、ということになります。


このオフセットを反映するには、対象間の角度を求める必要があります。
角度を求めた後、縦横成分を算出しオフセットを掛け合わせます。
それが以下の部分になります。

			// 対象ballBに対してballAを動かす
			// ballBとballAの距離
			var dx:Number = ballB.x - ballA.x;
			var dy:Number = ballB.y - ballA.y;
			// 縦横の距離から角度を算出
			var angle:Number = Math.atan2(dy, dx);

			// オフセットを加味して実際の"対象"を算出 −にすることで右上になる
			var targetX:Number = ballB.x - Math.cos(angle) * springLength;
			var targetY:Number = ballB.y - Math.sin(angle) * springLength;			

これは前々からちょくちょくでていてるので僕的には問題ないです。
上記で求めたtargetX、targetYが実際の移動対象となる位置になります。


その後のコードはこれまでと同じです。


コードはこちら。

package
{
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	
	public class DoubleSpring extends Sprite
	{
		private var ball1:Ball;
		private var ball2:Ball;
		// ドラッグしているかどうか
		private var ball1Dragging:Boolean = false;
		private var ball2Dragging:Boolean = false;
		// バネ係数、摩擦、オフセット
		private var spring:Number = 0.1;
		private var friction:Number = 0.9;
		private var springLength:Number = 100;
		
		public function DoubleSpring()
		{
			init();
		}
		
		private function init():void
		{
			// 2つのボールを配置
			ball1 = new Ball(20);
			ball1.x = 200 * Math.random();
			ball1.y = 200 * Math.random();
			ball1.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
			addChild(ball1);

			ball2 = new Ball(20);
			ball2.x = 200 * Math.random();
			ball2.y = 200 * Math.random();
			ball2.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
			addChild(ball2);

			addEventListener(Event.ENTER_FRAME, onEnterFrame);
			stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
		}


		private function onEnterFrame(evt:Event):void
		{
			// ドラッグしていないときは動いてよし
			if(!ball1Dragging){
				springTo(ball1, ball2)
			}
			if(!ball2Dragging){
				springTo(ball2, ball1)
			}
			
			// 線(バネ)の描画
			graphics.clear();
			graphics.lineStyle(1);
			graphics.moveTo(ball1.x, ball1.y);
			graphics.lineTo(ball2.x, ball2.y);
		}
		
		private function springTo(ballA:Ball, ballB:Ball):void{
			// 対象ballBに対してballAを動かす
			// ballBとballAの距離
			var dx:Number = ballB.x - ballA.x;
			var dy:Number = ballB.y - ballA.y;
			// 縦横の距離から角度を算出
			var angle:Number = Math.atan2(dy, dx);
			// オフセットを加味して実際の"対象"を算出 −にすることで右上になる
			var targetX:Number = ballB.x - Math.cos(angle) * springLength;
			var targetY:Number = ballB.y - Math.sin(angle) * springLength;			
			
			// 速度を設定
			ballA.vx += (targetX - ballA.x) * spring;
			ballA.vy += (targetY - ballA.y) * spring;
			// 摩擦
			ballA.vx *= friction;
			ballA.vy *= friction;
			// 移動
			ballA.x += ballA.vx;
			ballA.y += ballA.vy;
		}

		private function onMouseDown(evt:MouseEvent):void
		{
			// ドラッグ開始
			Ball(evt.target).startDrag();
			if(evt.target == ball1){
				ball1Dragging = true;
			}
			if(evt.target == ball2){
				ball2Dragging = true;
			}
		}

		private function onMouseUp(evt:MouseEvent):void
		{
			ball1.stopDrag();
			ball2.stopDrag()
			ball1Dragging = false;
			ball2Dragging = false;
		}
	}
}

import flash.display.Sprite;

class Ball extends Sprite
{
	private var radious:Number;
	private var color:uint;
	public var vx:Number = 0;
	public var vy:Number = 0;

	public function Ball(radius:Number = 40, color:uint = 0xff0000)
	{
		this.radious = radius;
		this.color = color;
		init();
	}

	private function init():void{
		graphics.beginFill(color);
		graphics.drawCircle(0, 0, radious);
		graphics.endFill();
	}
}


以上で、8章イージングとバネは終了です。


バネは簡単そうで、奥が深いと思いました。
イージングやバネ運動は、アイデア次第でいろんなエフェクトにいかせそうです。


以上、おしまい。