PS:自己翻译的,转载请著明出处
2-3创建一个第一人称射击游戏照相机: 颤抖式摄像机
问题
你想创建一个行动就象是第一人称的射击模式。用鼠标来旋转摄象机,用键盘来移动摄象机。
解决方案
章节2-2介绍摄像机的方法,只要检测到用户的输入就将更新摄象机的位置和旋转。您的摄象机的轮换矩阵 将改为根据鼠标的移动。上下按键将导致摄象机的位置前后移动,左右按键也会使摄象机左右移动。
如何实现
一般来讲,在第一人称游戏中,玩家可以自由左右旋转和无约束的上下看。这些移动是靠围绕上和右向量旋转响应的,甚至更详细点就是围绕(0,1,0)和(1,0,0)的方向旋转。
移动之前,阅读第4章的4-2的"乘法矩阵顺序"是非常有用的。本次讨论的底线是顺序乘法没有问题,当你想整合2次旋转第2次旋转的坐标是被第一的旋转改变的。
第一次的旋转影响到第2次的坐标系被称为方向锁;它有时候使你很痛苦,但是有时候这又是你需要的。这种情况下的第一人称相机,您首先使你的相机沿着垂直向上的坐标轴旋转和沿着向右的向量旋转。第一轮很好的转动你的Right vector,您可以直观的站立和伸展你的右手.如果你右转,手臂也会随你旋转,而现在,您可以很好周围旋转的方向您的手臂上下看。
简而言之,需要存储两个变量:向上方向和向右方向的旋转。当你需要计算总的旋转,使用“想上旋转后再向右旋转”的原则来整合。一旦你知道总轮换,可以简单地插入到这一点到前面章节的代码中。
现在,您已经知道第一人称相机旋转背后的概念,你仍然需要与用户输入的两个变量控制的amount of轮换。在这方面,例如鼠标输入的方法。你将决定改变鼠标位置两个更新周期和相应的变量的变化.所以,你需要这些变量:
1 float leftrightRot;
2 float updownRot;
3 Vector3 cameraPosition;
4 Matrix viewMatrix;
5 MouseState originalMouseState;
MouseState包含鼠标指针的位置,以及一些bit表明鼠标按键在上次调用Mouse.GetState()后是否被按下。给2个变量在初始化情况下赋值:
Code
leftrightRot=0.0f;
updownRot=0.0f;
cameraposition=new Vector3(1,1,0);
UpdateViewMatrix();
Mouse.SetPosition(Widnow.ClientBounds.Width/2,Windows.ClientBounds.Height/2);
originalMouseState=Mouse.GetState();
第一次的旋转值为0而且相机的位置是你开始选择其始点。一会要构造一个UpdateViewMatrix方法,它在这里被用来初始化viewMatrix变量。
下一步,您将鼠标光标移动到屏幕中央作为起始的位置。MouseState至少包含了你鼠标指针的中心位置信息,在更新循环中,可以检测新状态和上一次的状态是否不同,如果不同说明鼠标被移动了。
让我们来看看在例行的更新。正如前面所说,首先检查新的MouseState看它与originalMouseState存储的鼠标信息是否相同。如果不同,则旋转的值相应的更新,鼠标的点重新回到屏幕的中心:
Code
1 float rotationSpeed=0.005f;
2 MouseState currentMouseState=Mouse.GetState();
3 if(currentMouseState!=originalMouseState)
4 {
5 float xDifference=currentMouseState.X-originalMouseState.X;
6 float yDifference=currentMouseState.Y-originalMouseState.y;
7 leftrightRot-=rotationSpeed*xDifference;
8 updownRot-=rotationSpeed*yDifference;
9 Mouse.SetPosition(Window.ClientBounds.Width/2,Window.ClientBounds.Height/2);
10 }
11 UpdateViewMatrix();
rotationSpeed变量定义了摄象机会移动的多快。您找到的鼠标坐标新的和原来的位置之间横向和纵向差异,相应的调整围绕Right和Up向量的旋转矩阵。
最后一行重置鼠标指针到中心窗口。
注意:另一种方法可以存储currentMouseState在originalMouseState中,在下次更新循环中与新的currentMouseState比较。
然而,如果将鼠标光标移动走向边缘的屏幕上这将成为无用的。例如,鼠标光标点击屏幕右边缘上,当用户移动鼠标到最右边,鼠标的X的位置将不会有任何差异
。定位鼠标光标回到该中心的屏幕上解决了这个问题。
最后,调用UpdateViewMatrix方法,它将更新viewMatrix变量以适应新的旋转矩阵的值。UpdateViewMatrix方法的代码如下,
除了第一行,你可以找到详细的解释在上一页章节。基于相机存储旋转值的矩阵和相机位置的矩阵,这个方法计算了目标和上部vectors,
这些都被创建视景矩阵:
1 Matrix cameraRotation=Matrix.CreateRotationX(updownRot)*Matrix.CreateRotationY(leftrightRot);
2 Vector3 cameraOriginalTarget=new Vector3(0,0,-1);
3 Vector3 cameraOriginalUpVector=new Vector3(0,1,0);
4 Vector3 cameraRotatedTarget=Vector3.Transform(cameraOriginalTarget,cameraRotation);
5 vector3 cameraFinaltarget=cameraPositon+cameraRotatedTareget;
6 vector3 cameraRotatedUpVector=Vector3.Transform(cameraOriginalUpVector,cameraRotation);
7 Vector3 cameraFinalUpVector=cameraPosition+cameraRotatedUpVector;
8 viewMatrix=Matrix.CreateLookAt(cameraPosition,cameraFinalTarget,cameraFinalUpVector);
第一行用来解释说明。Right向量在这里是(1,0,0),它是x坐标轴的方向。这就是为什么使用CreateRotationX方法来创建这个旋转。Up向量在这里是(0,1,0),这是你用CreateRotationY方法创建左右的旋转。关于乘法的次序:M1*M2的意思是"M1在M2后面"这就成为了"先上下旋转后左右旋转"关于乘法矩阵的规则请看第4章4-2节。
当您将滑鼠移动光标时这将使您的相机旋转。接下来,您希望按下前后按键时摄象机前后运动。您将首先检测按键和反应,这是具体的向量,增加摄像头的位置:
1 keyboardState keyState=Keyboard.GetState();
2 if(keyState.IsKeyDown(keys.Up))
3 AddToCameraPosition(new Vector3(0,0,-1));
4 if(keyState.IsKeyDown(keys.Down))
5 AddToCameraPosition(new Vector3(0,0,1));
6 if(keyState.IsKeyDown(keys.Right))
7 AddToCameraPosition(new Vector3(1,0,0));
8 if(keyState.IsKeyDown(keys.Left))
9 AddToCameraPosition(new Vector3(-1,0,0));
压下上键导致相机的位置增加使它前进,压下下键导致相机的位置减少从而向后运动。虽然,你可以增加相机的位置的向量值但是你还是要用相机旋转矩阵去旋转它们。更多信息参看前面的章节。下面是AddToCameraPosition的处理方法:
Code
1 private void AddToCameraPosition(Vector3 vectorToAdd)
2 {
3 float moveSpeed=0.5f;
4 Matrix cameraRotation=Matrix.CreateRotationX(updownRot)*Matrix.CreateRotationY(leftrightRot);
5 Vector3 rotatedVector=Vector3.Transform(vectorToAdd,cameraRotation);
6 cameraPosition+=moveSpeed*rotatedVector;
7 UpdateViewMatrix();
8 }
首先,计算摄象机的旋转矩阵,正如UpdateViewMatrix方法所示。然后,您根据这一旋转矩阵来指定旋转矢量。旋转矢量乘以一个变量的可让您设定你相机的移动速度。
相机的位置被实实在在的改变了。最后,调用UpdateViewMatrix方法,它创建一个新的视景矩阵考虑新的摄像头位置。
代码
建立第一人称的摄象机,只需要跟踪4个变量:向上和向右的旋转,相机的位置,窗口中的鼠标中心响应的MouseState参数。键的下压和鼠标的移动在更新循环中响应,这些参数的触发了它的变化导致一个新的视景矩阵被创建基于这些新的变量值。
Code
1 protected override void Initialize()
2 {
3 base.Initialize();
4 float viewAngle=MathHelper.PiOver4;
5 float aspectRatio=(float)this.Window.ClientBounds.With/(float)this.Window.ClientBounds.Height;
6 float nearPlane=0.5f;
7 float farPlane=100.0f;
8 projectMatrix=Matrix.CreatePerspectiveFieldOfView(viewAngle,aspectRatio,nearPlane,farPlane);
9 leftrightRot=0.0f;
10 updownRot=0.0f;
11 cameraPosition=new Vector3(1,1,10);
12 UpdateViewMatrix();
13 Mouse.SetPosition(Window.ClientBounds.Width/2,Window.ClientBounds.Height/2);
14 originalMouseState=Mouse.GetState();
15 }
16 protected override void Update(GameTime gameTime)
17 {
18 if(GamePad.GetState(PlayerIndex.One).Buttons.Back==ButtonState.Pressed)
19 this.Exit();
20 float rotationSpeed=0.005f;
21 MouseState currentMouseState=Mouse.GetState();
22 if(currentMouseState!=originalMouseState)
23 {
24 float xDifference=currentMouseState.X-originalMouseState.X;
25 float yDifference=currentMouseState.Y-originalMouseState.Y;
26 leftrightRot-=rotationSpeed*xDifference;
27 updownRot-=rotationSpeed*yDifference;
28 Mouse.SetPosition(Window.ClientBounds.Width/2,Window.ClientBounds.Height/2);
29 UpdateViewMatrix();
30 }
31 keyboardState keyState=keyboard.GetState();
32 if(keyState.IsKeyDown(keys.Up))
33 AddToCameraPosition(new Vector3(0,0,-1));
34 if(keyState.IsKeyDown(keys.Down))
35 AddToCameraPosition(new Vector3(0,0,1));
36 if(keyState.IsKeyDown(keys.Right))
37 AddToCameraPosition(new Vector3(1,0,0));
38 if(keyState.IsKeyDown(keys.Left))
39 AddToCameraPosition(new Vector3(-1,0,0));
40 base.Update(gameTime);
41 }
42 private void AddToCameraPosition(Vector3 vectorToAdd)
43 {
44 float moveSpeed=0.5f;
45 Matrix cameraRotation=Matrix.CreateRotationX(updownRot)*Matrix.CreateRotationY(leftrightRot);
46 Vector3 rotatedVector=Vector3.Transform(vectorToAdd,cameraRotation);
47 cameraPosition+=moveSpeed*rotatedVector;
48 UpdateViewMatrix():
49 }
50 private void UpdateViewMatrix()
51 {
52 Matrix cameraRotation=Matrix.CreateRotationX(updownRot)*Matrix.CreateRotationY(leftrightRot);
53 Vector3 cameraOriginalTarget=new Vector3(0,0,-1);
54 Vector3 cameraOriginalUpVector=new Vector3(0,1,0);
55 Vector3 cameraRotatedTarget=Vector3.Transform(cameraOriginalTarget,cameraRotation);
56 Vector3 cameraFinalTarget=cameraPosition+cameraRotatedTarget;
57 Vector3 cameraRotatedUpVector=Vector3.Transform(cameraOriginalUpVector,cameraRotation);
58 Vector3 cameraFinalUpVector=cameraPosition+cameraRotatedUpVector;
59 viewMatrix=Matrix.CreateLookAt(cameraPosition,cameraFinalTarget,cameraRotatedUpVector);
60 }
晃动的相机类
因为它往往是非常有用的现成第一人称相机模式,把它单独分开独成1份。这个类经常被用在许多3D例子这本书也用,很容易融入一个自己的项目