本教程系列的目标是制作一款3D驾驶模拟游戏。随着每个教程的深入,将通过添加新的图形、物理和游戏玩法特性,使游戏变得更加炫酷。本教程的目的是模拟一辆汽车在无限大的平面上行驶。虽然这与现实世界并不完全相同,但会在后面的教程中逐步增加现实感。目前,请想象这个世界是一个没有障碍物的完美平面。
本文不是针对绝对初学者的。需要对XNA Game Studio 2.0有基本的了解。如果是初学者,建议如下:
理解公式需要对牛顿力学和几何学有合理的了解。
应用程序被分为三个类:Game1、Stuff、Car。类Stuff代表可以在屏幕上渲染的任何物质体。类Car是类Stuff的专业化,代表汽车。类Game1是主应用程序,使用Stuff和Car的对象。
首先让理解类Stuff。如果查看代码,会看到试图封装存储任何物质体信息并在屏幕上渲染所需的一切。由于代码相当直接,仅提供成员变量和函数的概述。
成员viewMatrix和projectionMatrix是静态的,因为它们对所有可渲染对象都是相同的。成员position和rotation分别代表物体在3D空间中的位置和方向。成员cameraRotation代表相机的方向。存储这个是必要的,以实现相机延迟的效果。成员model代表物体的3D模型。函数UpdateCamera根据汽车的位置和方向更新viewMatrix。函数Draw在屏幕上渲染3D模型。
现在来理解最困难的部分——理解类Car。这里有两个概念在起作用。第一个是踩油门、松开油门和刹车对汽车运动的影响。第二个是当方向盘向左或向右转动时汽车如何转弯。
第一个概念。在任何瞬间,汽车都处于某个档位,由变量gear表示。车辆的速度决定了gear。在变量topSpeeds中存储了每个档位的最高速度。档位x的最高速度也是档位x+1的最低速度。每个档位的加速度是不同的。
public void Accelerate()
{
free = false;
if ((gear < 7) && (speed > topSpeeds[gear]))
{
gear++;
}
if (gear == 0)
{
acceleration = accelerations[1];
}
else if (gear < 7)
acceleration = accelerations[gear];
else
{
gear = 6;
acceleration = 0;
speed = topSpeeds[6];
}
}
当刹车被应用时,效果是负加速度,其大小是该档位的加速度加上一个常数。这里还需要考虑倒车档和倒车的最高速度。
public void Brake()
{
free = false;
if ((gear > 0) && speed <= topSpeeds[gear - 1])
{
gear--;
}
if ((gear == 0) && (speed < -maxReverseSpeed))
{
acceleration = 0;
speed = -maxReverseSpeed;
}
else
{
acceleration = -accelerations[gear] - 6.7f;
}
}
当油门被释放时,即使这样,也需要应用一个负加速度,其大小与该档位的加速度相同。最终汽车会停下来。
public void Free()
{
free = true;
if ((gear > 0) && (speed < topSpeeds[gear - 1]))
{
gear--;
}
acceleration = -accelerations[gear];
}
为了确定汽车在每个瞬间的新位置,以下代码放置在Update方法中。
float speed = speed + acceleration * time;
float dist = speed * time;
Vector3 addVector = Vector3.Transform(new Vector3(0, 0, -1), rotation);
position += addVector * dist;
为了确保当油门和刹车都没有被按下时汽车最终停下来,使用以下代码:
float newSpeed = speed + acceleration * time;
if ((free == true) && (newSpeed * speed <= 0.0f))
{
gear = 1;
acceleration = accelerations[gear];
speed = 0.0f;
}
else
speed = newSpeed;
现在让理解汽车是如何转弯的。当驾驶员向左或向右转动方向盘时,平面表面与汽车车身形成一个角度theta,如图示。Theta应该均匀地从0增加到一个最大值。同时,当方向盘被释放时,theta最终应该变为零。代码如下:
if (turn == 0)
{
float newAngle = steerAngle;
if (steerAngle < 0.0f)
{
newAngle = steerAngle + time * steerSpeed;
}
else if (steerAngle > 0.0f)
{
newAngle = steerAngle - time * steerSpeed;
}
if (newAngle * steerAngle < 0.0f)
{
steerAngle = 0.0f;
}
else
steerAngle = newAngle;
}
else
{
if (turn == -1)
{
float newAngle = steerAngle - time * steerSpeed;
if (newAngle < -maxSteerAngle)
{
steerAngle = -maxSteerAngle;
}
else
{
steerAngle = newAngle;
}
}
else
{
float newAngle = steerAngle + time * steerSpeed;
if (newAngle > maxSteerAngle)
{
steerAngle = maxSteerAngle;
}
else
{
steerAngle = newAngle;
}
}
}
现在已经理解了轮胎是如何转动的,让弄清楚汽车是如何转弯的。仔细看看图:
会立刻明白常数HD、VD和L代表什么。O是3D模型的原点。假设汽车将围绕标记为“转弯中心”的点旋转,半径为r。可以使用三角学计算r的值:
float x = (VD / Math.Abs((float)Math.Tan(steerAngle)) + HD / 2);
float r = (float)Math.Sqrt(x * x + L * L);
现在有了半径。汽车旋转的角度可以计算出来,并相应地创建Quaternion。
float theta = speed * time / r;
if (steerAngle < 0.0f)
theta = -theta;
rotation = rotation * Quaternion.CreateFromAxisAngle(new Vector3(0, -1, 0), theta);
现在可以确定汽车的位置如下:
speed = speed + acceleration * time;
float dist = speed * time;
Vector3 addVector = Vector3.Transform(new Vector3(0, 0, -1), rotation);
position += addVector * dist;
其余的代码相当直接。