2-3.创建一个第一人称射击相机:振动式相机
问题
你想创建一个像许多第一人称射击游戏里的相机。你想用鼠标旋转,用键盘移动。
方案
从2-2介绍的方法开始,每当检测到用户输入就更新相机的位置或旋转。您的相机旋转矩阵的改变将依照鼠标的移动。按上或下方向键,相机将前进或后退,按左或右方向键,相机将左右平移。
如何运作
通常,第一人称游戏,玩家可以自由地四处看。这些运动相对应的分别旋转上和右向量,甚至更详细的绕(0,1,0)和(1,0,0)向量旋转。
围绕着前向量旋转,但是这是不允许的。这会导致玩家弯曲他的脖子到左边或右边,也没有采用第一人称,除了当玩家被枪杀并躺在地上。
继续之前,最好能读一下4-2'矩阵乘法规则',那会对你有帮助。你最少要知道是相乘的顺序很重要,因为当你组合两次旋转,第二次旋转的轴被第一次的旋转改变了。
第一次旋转对第二次旋转的影响称为‘Gimbal lock’;它有时可能是真正的痛苦,但它有时也能如你所愿。在第一人称中,你是幸运的:你将先绕向上的轴旋转,再绕向右的轴旋转。先绕着你的向上向量旋转很好,你可以想像站起来伸展你的右手。如果你向右转,你的手臂和你一起转,现在,你可以很好地绕着您的手臂的方向上下看。
总之,你要存储两个变量:顺着右和顺着上方向旋转的量。接下来,你要计算总的旋转,绕向上的轴旋转,再绕向右的轴旋转。这样就算出总旋转。
现在你知道的第一人称视点轮换背后的概念,你仍然需要持有用户输入的旋转量的两个变量。这里给出鼠标输入的例子。你要决定两次鼠标更新之间值的改变。所以代码如下:
float leftrightRot;
float updownRot;
Vector3 cameraPosition;
Matrix viewMatrix;
MouseState originalMouseState;
MouseState包含鼠标指针的位置,说明自上次调用Mouse.GetState方法鼠标按键是否按下。在Initialize方法给每个变量起始值:
leftrightRot = 0.0f;
updownRot = 0.0f;
cameraPosition = new Vector3(1,1,10);
UpdateViewMatrix();
Mouse.SetPosition(Window.ClientBounds.Width/2, Window.ClientBounds.Height/2,);
originalMouseState = Mouse.GetState();
你先把旋转值设为0并给相机选个初始位置。你会很快创建UpdateViewMatrix方法,用来简单初始化viewMatrix变量。
接下来,你设置鼠标指针到屏幕中心。最后一行储存MouseState,它包含鼠标的位置,在更新周期之间,你可以检查两个状态之间的变化,就知道鼠标是否被移动。
让我们查看更新历程。如果状态有变化,旋转值就更新,鼠标回到窗口中心:
float rotationSpeed = 0.005f;
MouseState currentMouseState = Mouse.GetState();
if(currentMouseState != originalMouseState)
{
float xDifference = currentMouseState.X – originalMouseState.X;
float yDifference = currentMouseState.Y – originalMouseState.Y;
leftrightRot -= rotationSpeed * xDifference;
updownRot -= rotationSpeed * yDifference;
Mouse.SetPosition(Window.ClientBounds.Width/2, Window.ClientBounds.Height/2);
}
UpdateViewMatrix();
rotationSpeed变量定义相机旋转的速度。你用鼠标两次状态之间的不同来旋转相机的右和上向量。最后一行重置鼠标到窗口中心。
注意 另一种做法可能是您存储currentMouseState在originalMouseState来比较下次更新的新currentMouseState。但是,如果鼠标移到窗口边缘就无效了。例如,鼠标指针已经碰到窗口右边界,用户再向右移动,鼠标的X位置不会变化。所以每次把鼠标重置到窗口中心。
最后,调用UpdateViewMatrix方法,更新旋转值。你可以前面章节找到详细的解释。基于矩阵存储相机的旋转和位置,这方法计算目标和上向量,需要创建视图矩阵。
Matrix cameraRotation = Matrix.CreateRotationX(updownRot) * Matrix.CreateRotationY(leftrightRot);
Vector3 cameraOriginalTarget = new Vector3(0, 0, -1);
Vector3 cameraOriginalUpVector = new Vector3(0, 1, 0);
Vector3 cameraRotatedTarget = Vector3.Transform(cameraOriginalTarget, cameraRotation);
Vector3 cameraFinalTarget = cameraPosition + cameraRotatedTarget;
Vector3 cameraRotatedUpVector = Vector3.Transform(cameraOriginalUpVector, cameraRotation);
Vector3 cameraFinalUpVector = cameraPosition + cameraRotatedUpVector;
viewMatrix = Matrix.CreateLookAt(cameraPosition, cameraFinalTarget, cameraRotatedUpVector);
右向量是(1,0,0),所以是沿着X轴的。这是用CreateRotationX方法的原因。上向量是(0,1,0) 所以是沿着Y轴的。这是用CreateRotationY方法的原因。M1*M2,表示先M2后M1,就是先左右,后上下。
当你移动鼠标指针这将让你的相机旋转。之后,当你按上方向键,相机前进。更新期间,先检查按键来决定相机位置:
KeyboardState keyState = Keyboard.GetState();
if (keyState.IsKeyDown(Keys.Up))
AddToCameraPosition(new Vector3(0, 0, -1));
if (keyState.IsKeyDown(Keys.Down))
AddToCameraPosition(new Vector3(0, 0, 1));
if (keyState.IsKeyDown(Keys.Right))
AddToCameraPosition(new Vector3(1, 0, 0));
if (keyState.IsKeyDown(Keys.Left))
AddToCameraPosition(new Vector3(-1, 0, 0));
QuakeCamera类
这在第一人称类型的游戏中很常用的类,我把他从本章分离出来,他在本书的很多例子中用到,你也能很容易的把他添加到自己的项目中。