4.4 使用加速度控制速度
问题
你想让模型能够漂亮的加速而不是从静止直接提高到最大速度。图4-9显示了你想实现的这种加速过程。
图4-9 模型的速度时间图像
解决方案
通过添加加速度,你可以定义你的模型的速度变化有多快。加速度就是每帧速度的增加量。
工作原理
你需要保存模型的位置和旋转,这是因为你需要知道哪个方向是向前的。在本例中,你只是让模型绕着它的向上的y轴旋转并沿着X和Z轴移动。如果你想让模型只基于X和Z轴沿着一个表面移动可参加教程4-17。
因为模型以后的速度取决于当前速度,所以你需要保存当前速度。你将存储速度矢量而不是标量。这个速度矢量包含模型当前前进的方向,它的长度表示速度的大小。所以在类中添加三个变量:
Vector3 modelPosition = new Vector3(); float modelYRot = 0; Vector3 modelVelocity= new Vector3();
你也可以定义模型的最大加速度和转弯速度:
const float modelMaxAcceleration = 30.0f; const float modelMaxTurnSpeed = 0.002f;
在Update方法中接受自上一帧以来经过的时间为参数,检测模型移动的距离:
float elapsedSeconds = (float)gameTime.ElapsedGameTime.Milliseconds / 1000.0f; float forwardReq= 0; float angleReq = 0; if (keyState.IsKeyDown(Keys.Up)) forwardReq += 1.0f; if(keyState.IsKeyDown(Keys.Down)) forwardReq -= 1.0f; if (keyState.IsKeyDown(Keys.Left))angleReq += 1.0f; if (keyState.IsKeyDown(Keys.Right)) angleReq -= 1.0f;
当模型向前加速时变量forwardReq为正,减速或向后加速时为负。变量angleReq表示模型左转还是右转。
在光滑表面上加速
下面的代码添加基本加速行为:
Matrix rotMatrix = Matrix.CreateRotationY(angle); Vector3 forwardDir = Vector3.Transform(new Vector3(0, 0, -1), rotMatrix); velocity = velocity + elapsedTime * forwardReq *maxAccel *forwardDir; modelPosition += velocity; modelYRot += rotationReq * maxRotSpeed* velocity.Length();
前两行代码计算模型当前的Forward矢量,这个矢量用来获得模型加速的方向。这个Forward向量是在向上y-轴上附加在默认的(0,0,-1) Forward方向上的旋转获得的。
注意:本教程中的模型只能绕y轴旋转,所以Forward向量只是基于绕向上y轴的旋转。可参加教程2-3和2-4学习计算整个3D空间或四元数的旋转。
接下来的代码计算速度。在前一个速度的基础上基于用户输入添加一个新的矢量。自上一帧经过的时间越长,越需要调整这个速度矢量。另外,加速度越大,越需要调整这个速度矢量。最后你还要考虑模型的最大加速度。将这三个因素相乘获得这一帧需要调整多少速度。因为你需要将一个Vector3添加到速度矢量上,要乘以带有forwardDir的这个值获取想要添加的矢量。
注意:如果没有旋转,速度和moveDirection会指向相同的方向,只是简单使速度矢量变大让模型移动地更快。
最后,这个Vector3添加到模型的位置,模型的旋转被调整。模型运动得越快,转向也越快。
当使用这个代码时,你注意到有两个缺点。首先,模型将一直加速,它没有一个最大速度。你想如图4-9所示增加速度并终止于一个确定的最大速度。使用这个代码,模型将一直以相同的步进加速。
第二,如果模型在一个方向上的速度很大,在旋转模型后,它可能仍在相同的方向。如果模型是在一个冰面上当然不错,但通常这并不是你想要的结果。
添加摩擦力
在真实情况中,模型的速度会因为模型与空气、表面之间的摩擦等慢慢减小。当你停止加速,摩擦力会让速度减小直至停止。当持续加速,摩擦力会导致速度增加到一个特定值不在增加。
你可以通过减去前面的速度获取摩擦力。下面的代码添加了摩擦力:
velocity = velocity * (1 - friction * elapsedTime) + elapsedTime * forwardReq *maxAccel *forwardDir;
两帧之间的时间越长,摩擦力的效果越明显,所以你要根据流逝时间乘以friction变量。
让模型保持在向前方向运动
虽然模型现在已经以一个比较自然的方式加速了,但在它旋转时仍有一点瑕疵(除非是在空间游戏或冰面滑行游戏中)。通常,你只想让模型沿着前进方向移动。
你需要知道沿着Forward 方向上的速度矢量是多少。这可以通过点乘得到:它将Velocity (V) 矢量投影到Forward (F)上,如图4-10所示,并返回投影速度的大小。
图4-10 将速度矢量投影到Forward矢量上
所以在更新速度后使用下列代码:
float forwardSpeed = Vector3.Dot(velocity, forwardDir); velocity = forwardSpeed * forwardDir; modelPosition += velocity * elapsedTime; modelYRot+= rotationReq * maxRotSpeed * forwardSpeed;
forwardSpeed变量表示Velocity矢量在Forward方向上分量的大小,然后乘以Forward方向并将结果存储在一个新的Velocity矢量中。通过这个方法,你可以保证模型将沿着向前方向移动。
注意:使用forwardSpeed变量的一个额外好处是当模型向前运动是它为正,向后运动时它为负,而velocity. Length ()总是正的。
代码 Accelerate方法根据加速度调整模型的位置,速度和旋转,将它们和最大加速度和旋转速度相加。你还要获取用户输入和摩擦力变量。
private float Accelerate(ref Vector3 position, ref float angle, ref Vector3 velocity,float forwardReq, float rotationReq, float elapsedTime, float maxAccel, float maxRotSpeed,float friction) { Matrix rotMatrix = Matrix.CreateRotationY(angle); Vector3 forwardDir= Vector3.Transform(new Vector3(0, 0, -1), rotMatrix); velocity = velocity * (1- friction * elapsedTime) + elapsedTime * forwardReq * maxAccel *forwardDir; loat forwardSpeed = Vector3.Dot(velocity, forwardDir); velocity = forwardSpeed * forwardDir; modelPosition += velocity * elapsedTime; modelYRot += rotationReq * maxRotSpeed* forwardSpeed; return forwardSpeed; }
这个方法返回forwardSpeed变量,若模型向后运动则为负值。
扩展阅读
你可以通过允许乘以加速度施加在模型上扩展这个方法,例如,重力加速度,你可以加上这个加速度将总和作为forwardDir。
发布时间:2009/9/1 下午12:03:10 阅读次数:5925