4-4. 用加速度来控制速度
问题
您想使您的模型加速很好,而不会从全速状态立即停止,反之亦然。下图显示一种你想要的加速度示意图。
方案
定义加速度,你可以定义你的物体如何加速。这是你加到模型的每一帧的数值。
如何运作
你要跟踪位置和模型的朝向,因为你要知道朝那个方向。本例中,你使模型只绕着Y轴旋转,只改变位置的XZ坐标。
还有,因为下一刻的速度取决于当前的速度,所以你要跟踪当前速度。比用数字更好的方法是用速度向量来储存速度。向量包含模型前进的方向,长度表示速度。所以加这3个变量到你的类中:
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();
前两行计算模型当前向前向量,因为需要知道模型的加速方向。这个向前向量是由默认(0,0,-1)向量绕向上Y轴旋转的。
注意 本节,模型只能绕Y轴旋转,所以向前方向只能绕Y轴。
第三行计算速度。你从上一个速度开始,第二个增量基于用户输入。传递的次数越多,你越需要调整这个速度。用户要求的加速度越大,你也越需要调整这个速度。作为最后的因素,你把最大加速度写到账户。把所有3个向量乘到一起,得到这一帧你总共要调整多少速度。你用forwardDir向量乘这个值得出你要加到这一帧的速度向量。
注意 如果没有被应用旋转,无论速度和moveDirection将指向同一个方向,这使速度向量越来越大,使您的模型更快。
最后,Vector3加到模型的位置,并旋转模型。模型移动越快,就旋转得越快。
用这个代码,要注意两个缺陷。第一,你的模型不会减速且没有最大速度。你想像4-9一样增加速度并在某个最大速度饱和。用这个代码,你的模型会保持相同加速度加速。
第二,如果模型非常快的转入一个方向,你旋转模型后,他还是朝着相同的方向前进。如果是在冰上,这非常好,但实际上可能不是你想要的。
添加摩擦
事实上,模型的速度会由于和空气,平面等的摩擦力减少,也适用内部机器。当你停止模型加速,摩擦会使你的模型减速直到完全停止。当你保持加速,摩擦会让你的速度在一个水平饱和。代码如下:
Velocity = velocity * (1 – friction * elapsedTime) + elaspedTime * forwardReq * maxAccel * forwardDir;
两帧之间有更多次的摩擦影响,所以你要把传入的摩擦值和摩擦次数的相乘。
只保持向前部分
虽然你的模型有更自然的方法加速,但当他旋转时仍移动的有点笨拙。通常你只想你的模型沿着向前方向移动。
为此,你想知道模型沿当前方向的速度向量。这就是点积要做的:组合速度向量在向前向量上,返回组建后的向量长度。
所以更新速度后用如下代码:
Float forwardSpeed = Vector3.Dot(velocity, forwardDir);
Velocity = forwardSpeed * forwardDir;
modelPosition += velocity * elapsedTime;
modelYRot += rotationReq * maxRotSpeed * forwardSpeed;
forwardSpeed变量指出速度向量的向前部分。你把这个值和向前方向乘并存为新速度向量。这样模型只沿着向前方向移动。
注意 forwardSpeed的额外的优势是不论模型向前向后都表示为绝对值。这和velocity.Length()矛盾,它永远返回绝对值。
扩展阅读
您可以扩展此说明到对象允许多个加速度对模型的影响,例如,在重力加速度。您可以通过综合加速度,并把它作为forwardDir。