关于物体运动的小研究

最近导师布置了一个任务:做一个与刚体运动有关的赛车类小游戏。
遂开始翻看《Foundation AS3.0 Animation》
草草阅览了一下,发现无非还是那些物理公式。
很快写了一个小程序来进行测试。

首先想到,可以把汽车看成一个整体,阻力简单处理为摩擦力。
那么,需要用到以下的物理知识:
汽车发动机功率恒定 P = F * v
牛顿力学公式 a = F / m
动摩擦力始终与物体运动方向相反汽车最终可以保持恒定的速度 v_max = P / f ,其中f为阻力
之后就牵扯到了阻力问题,假设除了地面给的摩擦力之外没有其它阻力,则 f = miu * m * g,其中miu为摩擦系数


做用户键盘输入的处理时,想起了很久以前玩的一个匿名作者做出来的不入流的小游戏。在这个小游戏中,用户每按一次键就可以让物体动一次,结果导致按键快的就跑得快,非常影响游戏平衡性。
于是,在这里我采取了一种经典模式——监听 KEY_DOWN 和 KEY_UP 事件,在 KEY_DOWN 时设置对应标记位,在 KEY_UP 时取消标志位,在 onEnterFrame 时根据标志位的不同给予相应处理。 这种置标记位后处理的模式有一个明显的好处:用户的连续快速多次按键不会对游戏产生影响。

当用户按下"↑"、"←"、"→"键的时候,汽车发动机将提供牵引力,使其速度发生改变。
对用户按"↓"键的处理,我一开始犯了一个小错误:为汽车发动机提供了一个与运动方向相反的力。这就导致当汽车静止的时候用户按下这个键,汽车将会发生很囧的往复颤抖。之后突然顿悟(果然是很久没碰物理了,脑袋都钝了)——刹车的原理应该是提高摩擦力… 于是,当downKey标志位为true时,简单增大摩擦力就可以了…

于是,onEnterFrame逻辑大概是这样子的:
1、如果速度不为0的话,计算摩擦力(如果用户按了刹车键,摩擦系数将增大)
2、判断用户按键方向(左,左前,前,右前,右),计算牵引力的角度rad
3、计算牵引力,即
F_x = Math.cos(rad) * P / |v|
F_y = Math.sin(rad) * P / |v|
4、计算合力,计算加速度
5、根据加速度改变速度
6、根据速度改变汽车的位置


使用这样的逻辑在用户体验上还是有很大的问题的,主要表现在:
1、用户的向左向右的操作在汽车上反应得很慢,因为其直接影响的是汽车的加速度而不是速度。这种迟滞性在拐弯时特别明显,非常损伤用户体验。
2、用户在汽车静止时按下左、右键,汽车会有剧烈抖动(rotation有剧烈改变)。


针对问题2,在逻辑中加入静止拐弯逻辑即可。

针对问题1,看来是需要分析一下汽车的运动过程了:如果把汽车看做一个整体的话,拐弯过程显然会失实。但如果不看成一个整体的话,需要考虑的各种力都很多,设计和计算都有可能相当复杂,计算过程可能会占用大量CPU资源。

因此考虑将运动过程简化:
用户按下"→"、"←"键的时候,只改变物体的朝向;
用户按下"↑"键的时候,汽车提供在运动方向上的牵引力;
用户按下"↓”键的时候,阻力增大.


由于此种运动处理已然事实,加入过多物理计算会显得纠缠不清,故考虑简化参量,将原先设置的物体质量等一系列物理参数全部修改掉。取出对所谓“力”等一系列“物理量”的考虑,直接以加速度为参量进行计算。之前的计算中由于存在非物体运动方向上的力,因此需要用二维矢量来计算。修改逻辑后,平面中不存在非rotation方向的外力了,因此我们可以将其简化为一维浮点数。

根据上述思路,onEnterFrame逻辑更改为:
1、总加速度置0。
2、如果速度不为0的话,将摩擦力产生的负加速度累加至总加速度(要考虑用户有没有按下"↓”)。
3、如果用户按下"↑",则将运动方向牵引力产生的加速度累加至总加速度。
4、如果用户按下了"→"、"←",适当调整物体的rotation
5、根据加速度改变速度
6、根据速度和rotation改变汽车的位置


其中,阻力是与速度成正相关的,至于具体公式这个尚需推敲。
简单起见,认为阻力与速度成正比。
各种参数都需要仔细斟酌,经过数次调优,最终的程序代码如下:

package {
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	
	public class RaceCar extends Sprite
	{
		public var v:Number;
		public function RaceCar()
		{
			super();
			var theShape:Shape = new Shape();
			theShape.graphics.beginFill(0x000000);
			theShape.graphics.moveTo(40,0);
			theShape.graphics.lineTo(-40,20);
			theShape.graphics.lineTo(-40,-20);
			theShape.graphics.lineTo(40,0);
			theShape.graphics.endFill();
			addChild(theShape);
			v = 0;
		}
	}

	public class MotionTest extends Sprite
	{
		private var raceCar:RaceCar;
		private var bg:Shape;
		
		private var upKey:Boolean;
		private var downKey:Boolean;
		private var leftKey:Boolean;
		private var rightKey:Boolean;
		
		private const a_f_mul:Number = 0.1;
		private const a_F:Number = 3; 
		
		
		public function MotionTest()
		{
			
			bg = new Shape();
			bg.graphics.beginFill(0xffffff);
			bg.graphics.drawRect(0,0,800,600);
			bg.graphics.endFill();
			addChild(bg);
			
			raceCar = new RaceCar();
			raceCar.x = 400;
			raceCar.y = 300;
			addChild(raceCar);
			
			stage.addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown);
			stage.addEventListener(KeyboardEvent.KEY_UP,onKeyUp);
			stage.addEventListener(Event.ENTER_FRAME,onEnterFrame);
			
			
		}
		
		private function onKeyDown(e:KeyboardEvent):void
		{
			switch(e.keyCode)
			{
				case 38:
				upKey = true;
				break;
				case 40:
				downKey = true;
				break;
				case 37:
				leftKey = true;
				break;
				case 39:
				rightKey = true;
				break;
			}
		}
		
		private function onKeyUp(e:KeyboardEvent):void
		{
			switch(e.keyCode)
			{
				case 38:
				upKey = false;
				break;
				case 40:
				downKey = false;
				break;
				case 37:
				leftKey = false;
				break;
				case 39:
				rightKey = false;
				break;
			}			
		}		
		
		private function onEnterFrame(e:Event):void
		{
			var a_sum:Number = 0;
			
			if(raceCar.v != 0)
			{
				a_sum -= (downKey)? a_f_mul * raceCar.v * 10 : a_f_mul * raceCar.v;
			}
			
			if(upKey) a_sum += a_F;
			
			if(leftKey && !rightKey) raceCar.rotation -= 80 / (10 + raceCar.v) ;
			
			if(rightKey && !leftKey) raceCar.rotation += 80 / (10 + raceCar.v) ;
			
			raceCar.v += a_sum;
			
			if(raceCar.v < 0.1) raceCar.v = 0;
			
			var rad:Number = raceCar.rotation * Math.PI / 180;
			raceCar.x += raceCar.v * Math.cos(rad);
			raceCar.y += raceCar.v * Math.sin(rad);
			
			if(raceCar.x > 800) raceCar.x -= 800;
			if(raceCar.x < 0) raceCar.x += 800;
			if(raceCar.y > 600) raceCar.y -= 600;
			if(raceCar.y < 0) raceCar.y += 600;
			
		}
	}
}



你可能感兴趣的:(设计模式,游戏,F#,Flash,actionscript)