4.4 使用加速度控制速度

问题

你想让模型能够漂亮的加速而不是从静止直接提高到最大速度。图4-9显示了你想实现的这种加速过程。

图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

图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

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号