Unity第三⼈称射击游戏视⾓控制与武器瞄准
最近练习Unity,想做⼀个第三⼈称射击游戏的Demo。⾸先来做⼀个武器瞄准效果,即让枪能指向⽬标。
查了很久不知道怎么实现,后来查到了IK这个概念,IK就是反向动⼒学,按我的理解是让⾻骼中的下层带动上层运动,与FK(前向动⼒学)相反,⽐如伸⼿去触摸物体时带动整个⾝体前倾。所以武器的瞄准就可以⽤通过IK来做,让⼿和武器瞄准⽬标,带动⾝体旋转,这样就很⾃然。
⽤代码来设置瞄准的思路是:从摄像机的位置向摄像机的forward⽅向发出⼀定距离的射线,如果射线打到物体就让把它设为瞄准⽬标,否则瞄准射线的终点。注意射线可能需要屏蔽玩家本⾝碰撞体的⼲扰。
设置AimIK的target位置的代码为:
aimIK.solver.target.position = targetPos;
例如射线打到地⾯:
-------------------------------------------
然后是视⾓控制,虽然Unity有相应的插件,但是直接⽤就没意思了。本来以为⽐较简单,没想到还是搞了⼏天(太菜了)
实现的功能如下:
1.
2.
3.
4.
5.
下⾯依次介绍,完整代码在最下⾯
1.摄像机平滑跟随
记录摄像机与⼈物的位置偏移向量
playerOfft = player.position - transform.position;
当摄像机旋转后需要更新playerOfft,再次计算上⾯这句代码。
更新playerOfft后需要在下⼀帧使⽤插值让摄像机移动到player.position - playerOfft这个位置
transform.position = Vector3.Lerp(transform.position, player.position - playerOfft, moveSpeed * Time.deltaTime);
2.摄像机绕玩家旋转,并限制⾓度
主要使⽤transform.RotateAround函数,让摄像机绕⼈物旋转。由于⼈物可以移动,⽽摄像机的位置是使⽤插值改变的,当⼈物移动时,playerOfft这个向量的长度和旋转⾓度会发⽣改变,⼈物⾛的越远,偏差会越⼤,不符合要求。
我的办法是当摄像机绕玩家旋转后,让playerOfft向量同步旋转相同的⾓度,更新playerOfft。旋转后playerOfft的长度不变,这样可保证player.position-playerOfft始终是摄像机的最终位置。
如下图⽰意:
RotateAround的⽤法为:
RotateAround(Vector3 point, Vector3 axis,float angle);
point:要围绕的点;
axis:要围绕的轴,如x,y,z
angel:旋转的⾓度
所以摄像机⽔平和垂直绕玩家旋转为:(注意旋转的轴不同)
transform.RotateAround(player.position, Vector3.up, axisX);
transform.RotateAround(player.position, transform.right,-axisY);
其中axisX和axisY为对应⿏标的偏移量xTime.deltatimex旋转速度:
float axisX = Input.GetAxis("Mou X")* rotateSpeed * Time.deltaTime;
float axisY = Input.GetAxis("Mou Y")* rotateSpeed * Time.deltaTime;
⽽让playerOfft向量绕⼀个点旋转则要先获取⽔平和垂直旋转的四元数:(同样注意绕的轴)
Quaternion rotX = Quaternion.AngleAxis(axisX, Vector3.up);
Quaternion rotY = Quaternion.AngleAxis(-axisY, transform.right);
然后让这两个四元素与向量相乘就可以旋转向量⽽长度不变:(注意这⾥相乘不具有交换性,不要改变顺序或是简写)
playerOfft = rotX * rotY * playerOfft;
要限制⾓度,我们要先让它旋转,然后获取旋转后垂直⽅向的欧拉⾓:
float x =(ation).eulerAngles.x;
但是欧拉⾓的范围是0-360度循环的,不利于判断,要转换范围成-180度-180度,向上为负,向下为正。然后判断它是否在我们给定的范围内如果超出范围则还原摄像机在垂直⽅向的旋转,并且让playerOfft只进⾏⽔平旋转;否则让playerOfft进⾏⽔平和垂直的旋转。
//欧拉⾓范围为0~360,这⾥要转为-180~180⽅便判断
if(x >180) x -=360;
if(x < minAngle || x > maxAngle)//超出⾓度
{
/
/还原位置和旋转
transform.position = posPre;
//更新offt向量,offt与本物体同步旋转
//我们需要通过这offt去计算本物体(包括摄像机)应该平滑移向的位置
//如果仅仅使⽤RotateAround函数,当⼈物在移动时会出现误差
playerOfft = rotX*playerOfft;
}
el//垂直视⾓符合范围的情况
{
//更新offt向量,offt与本物体同步旋转
playerOfft = rotX * rotY * playerOfft;
}
最⾼视⾓:(根据minAngle,根据需要设定,我设为-40)
最低视⾓:(根据maxAngle,根据需要设定,我设为50)
3.往上看时随⾓度增⼤⽽拉近摄像机,往下看时随⾓度增⼤⽽拉远摄像机
摄像机与⼈物的距离需要时可变的,⽐如在瞄准时应该将摄像机拉近⼈物,但是如果直接改变摄像机的位置⼜会破坏跟随玩家和⾃由视⾓的功能。
为了使摄像机在能够根据需要偏移的同时⼜不影响平滑跟随玩家,我的办法是把上⾯的控制代码挂在⼀个空物体上,将摄像机作为这个空物体的⼦物体,这样空物体跟随玩家时,摄像机也会做相同的位移。⽽让摄像机的位置产⽣偏移只需要改变它的localPosition,也就是它与⽗物体的相对位置。
如图:TPSCameraParent为空物体
摄像机偏移是改变TPSCamera的localPosition.z,让TPSCamera相对于TPSCameraParent前后移动。定义⼀个总的偏移量:
float localOfft =0;
这个偏移量的影响因素有三个:
1.垂直视⾓⾓度
2.是否瞄准
3.是否有遮挡
下⾯来看垂直视⾓⾓度的影响:
上⾯我们获取到了垂直⽅向旋转欧拉⾓float x,就可以根据x与我们给定的最⼤⾓度的⽐值来设定摄像机的前后偏移:
//更据⾓度设置摄像机位置偏移
if(x <0)//往上⾓度为负
{
//往上看时距离拉近
localOfftAngle =(x / minAngle)* localOfftAngleUp;
}
el
{
//往下看时距离拉远
localOfftAngle =-(x / maxAngle)* localOfftAngleDown;
}
其中localOfftAngle为根据⾓度计算的偏移量,localOfftAngleUp和localOfftAngleDown分别为向上看和向下看时这个偏移量的最⼤值。当x=0时即摄像机平视前⽅时,这个偏移量=0。
那怎么让摄像机能够前后偏移呢?让总偏移量加上它:
localOfft+=localOfftAngleMax;
在最后使摄像机平滑移动到偏移位置:
Vector3 offtPos = new Vector3(0,0, localOfft);//这是相机应该移向的位置
//使相机平滑移动到这个位置
俯视时拉远摄像机:
仰视时拉近摄像机:
4.瞄准时拉近摄像机,停⽌瞄准时回到默认位置
规定当⿏标右键按住时瞄准,松开时停⽌瞄准。
再定义⼀个偏移量localOfftAim和⼀个bool值isAiming
public float localOfftAim =2;//根据是否瞄准⽽产⽣的偏移量,表⽰瞄准时摄像机应该前进多远距离,根据需要设值private bool isAiming = fal;//是否正在瞄准
然后每帧判断⿏标事件:
if(Input.GetMouButtonDown(1))//⿏标右键按下为瞄准
{
isAiming = true;
}
if(Input.GetMouButtonUp(1))//⿏标右键松开停⽌瞄准
{
isAiming = fal;
}
接着根据isAiming来决定是否让localOfft加上这个偏移量:
//根据是否瞄准⽽调整
if(isAiming)
{
localOfft += localOfftAim;
}