在第一人称射击(FPS)游戏中,控制枪械的后坐力是为了增加游戏的真实性和挑战性。开枪后的后坐力通常会导致玩家的视角和枪口偏移,影响接下来的射击精准度。以下是一些常见的方法来模拟和控制后坐力:
视角偏移:
当玩家开枪时,可以通过编程让玩家的视角向上或者侧向偏移,模拟因后坐力导致的枪口跳动。这种偏移通常是瞬间的,并且随后会有一个恢复过程,视角逐渐回到原位。
实现步骤:
枪械动画:
可以为枪械创建后坐力动画,在射击时播放这个动画。动画可以在3D建模软件中预先制作好,也可以通过程序动态生成。
实现步骤:
枪械物理模拟:
使用物理引擎来模拟枪械的后坐力效果。这种方法可以使得后坐力更加自然和可预测。
实现步骤:
结合这些方法,开发者可以创造出符合游戏设计需求的后坐力效果,使得射击体验既具有挑战性又富有乐趣。在实际应用中,通常会根据不同武器的特性来调整后坐力的大小和表现方式,以便玩家可以通过技能和习惯来掌握每种武器的射击特点。
本文主要是探究第一种办法,因为枪械的射击通常我们会选择射线投射的方式实现,要实现后座力效果和第一种最匹配。
射击的具体实现可以看我之前的文章:一个通用的FPS枪支不同武器射击控制脚本
我们先做摄像机给摄像机添加一个父类,实现后座力的原理就是控制摄像机这个父类的xy旋转偏移值
public class GunRecoil : MonoBehaviour
{
private Vector2 recoil; // 后坐力向量,存储X和Y方向上的偏移量
public float addSpeed = 0.1f;// 后坐力增加速度
public float subSpeed = 10f;// 后坐力减少速度
void Update()
{
recoil.x = Mathf.MoveTowards(recoil.x, 0, subSpeed * Time.deltaTime);
recoil.y = Mathf.MoveTowards(recoil.y, 0, subSpeed * Time.deltaTime);
// 将recoil.y应用到物体的X轴旋转角度,将recoil.x应用到物体的Y轴旋转角度
transform.localEulerAngles = new Vector3(-recoil.y, recoil.x, 0);
//测试
if (Input.GetKey(KeyCode.Mouse0))
{
AddRecoil();
}
}
// 添加后坐力
public void AddRecoil()
{
recoil.x += Random.Range(-1, 1) * addSpeed;
recoil.y += addSpeed;
}
}
效果
我们想要更加精准,可以修改addSpeed和subSpeed为Vector2值的,当然我们不希望后座力导致视角一直往上抬,需要限制一个值
private Vector2 recoil; // 后坐力向量,存储X和Y方向上的偏移量
public Vector2 addSpeed = new Vector2(0.5f, 0.75f); // 后坐力增加速度
public Vector2 subSpeed = new Vector2(3f, 5f); // 后坐力减少速度
public Vector2 maxRecoil = new Vector2(1, 5); // 后坐力的最大值
void Update()
{
// 使用Mathf.MoveTowards方法逐渐将recoil.x和recoil.y减少到0
// subSpeed.x和subSpeed.y分别表示在每秒内减少的量,乘以Time.deltaTime可以使速度与帧率无关
recoil.x = Mathf.MoveTowards(recoil.x, 0, subSpeed.x * Time.deltaTime);
recoil.y = Mathf.MoveTowards(recoil.y, 0, subSpeed.y * Time.deltaTime);
// 将recoil.y应用到物体的X轴旋转角度,将recoil.x应用到物体的Y轴旋转角度
transform.localEulerAngles = new Vector3(-recoil.y, recoil.x, 0);
//测试
if (Input.GetKey(KeyCode.Mouse0))
{
AddRecoil();
}
}
// 添加后坐力
public void AddRecoil()
{
// // 通过随机取值范围[-1, 1]来增加recoil.x的值,并限制在-maxRecoil.x和maxRecoil.x之间
recoil.x = Mathf.Clamp(recoil.x + Random.Range(-1, 1) * addSpeed.x, -maxRecoil.x, maxRecoil.x);
// // 增加recoil.y的值,并限制在0和maxRecoil.y之间
recoil.y = Mathf.Clamp(recoil.y + addSpeed.y, 0, maxRecoil.y);
}
效果
修改GunRecoil
public static GunRecoil Instance;
private void Awake() {
Instance = this;
}
public void AddRecoil(int xDir)
{
// // 通过随机取值范围[-1, 1]来增加recoil.x的值,并限制在-maxRecoil.x和maxRecoil.x之间
recoil.x = Mathf.Clamp(recoil.x + xDir * addSpeed.x, -maxRecoil.x, maxRecoil.x);
// // 增加recoil.y的值,并限制在0和maxRecoil.y之间,纵向后座力一般只有往上的
recoil.y = Mathf.Clamp(recoil.y + addSpeed.y, 0, maxRecoil.y);
}
//。。。
[System.Serializable]
public struct AmmoPosData
{
public Data[] datas; // 子弹数量对应的数据
// 根据子弹数量获取子弹偏移方向
public int GetDir(int ammoCount)
{
int maxId = datas.Length - 1;
int nextId = 0;
Data dt;
// 在数据列表中查找匹配的数据
do
{
dt = datas[nextId];
nextId++;
} while (nextId <= maxId && ammoCount > dt.ammo);
// 根据随机数确定偏移方向
float random = Random.Range(0, 1f);
if (random < dt.left)
{
return -1; // 左偏移
}
else if (random < dt.left + dt.right)
{
return 1; // 右偏移
}
return 0; // 不偏移
}
[System.Serializable]
public struct Data
{
public int ammo; // 子弹数量
public float left; // 左偏移概率
public float right; // 右偏移概率
}
}
修改GunSystem调用,及发射子弹脚本
private int shootAmmo;//射击第几颗子弹
public AmmoPosData ammoData;
private void Update(){
// 射击
if (readyToShoot && shooting && !reloading && bulletsLeft > 0)
{
bulletsShot = bulletsPerTap;
Shoot();//射击脚本
}
if (!shooting) shootAmmo = 0;
}
private void Shoot()
{
shootAmmo++;
GunRecoil.Instance.AddRecoil(ammoData.GetDir(shootAmmo));
//。。。
}
七字型
的弹道,那就先让前四发子弹往右,后面有十发子弹往左,最后的区间就五五开修改GunRecoil
//。。。
[System.Serializable]
public struct AmmoPosData
{
public float mirrorRate;//镜像概率
public Data[] datas; // 子弹数量对应的数据
// 根据子弹数量获取子弹偏移方向,和是否镜像
public int GetDir(int ammoCount, bool mirror)
{
int maxId = datas.Length - 1;
int nextId = 0;
Data dt;
// 在数据列表中查找匹配的数据
do
{
dt = datas[nextId];
nextId++;
} while (nextId <= maxId && ammoCount > dt.ammo);
// 根据随机数确定偏移方向
float random = Random.Range(0, 1f);
if (random < dt.left)
{
return mirror ? 1 : -1; // 左偏移
}
else if (random < dt.left + dt.right)
{
return mirror ? -1 : 1; // 右偏移
}
return 0; // 不偏移
}
//按概率判断是否镜像
public bool GetMirror()
{
if(Random.Range(0, 1f) < mirrorRate)
return true;
return false;
}
[System.Serializable]
public struct Data
{
public int ammo; // 子弹数量
public float left; // 左偏移概率
public float right; // 右偏移概率
}
}
修改GunSystem调用,及发射子弹脚本
bool isMirror;
private void Update(){
// 射击
if (readyToShoot && shooting && !reloading && bulletsLeft > 0)
{
bulletsShot = bulletsPerTap;
Shoot();//射击脚本
}
if (!shooting) shootAmmo = 0;
}
private void Shoot()
{
shootAmmo++;
//一般在第一发子弹决定弹道的镜像
if(shootAmmo == 1)isMirror = ammoData.GetMirror();
GunRecoil.Instance.AddRecoil(ammoData.GetDir(shootAmmo, isMirror));
//。。。
}
测试可以看到弹道有一半的概率变成镜像
现在的弹道太规律了,子弹是指哪里就打哪里,然而实际游戏中子弹可能会根据落点区间的大小基于瞄准射线进行一定的偏移,准星的大小就侧面体现了落点区间的大小
其实就是定义射击时的散布度
[Tooltip("射击时的散布度")]
public float spread;
private void Shoot()
{
shootAmmo++;
//一般在第一发子弹决定弹道的镜像
if(shootAmmo == 1)isMirror = ammoData.GetMirror();
GunRecoil.Instance.AddRecoil(ammoData.GetDir(shootAmmo, isMirror));
// 散布
float x = Random.Range(-spread, spread);
float y = Random.Range(-spread, spread);
// 计算带有散布的射击方向
Vector3 direction = fpsCam.transform.forward + new Vector3(x, y, 0);
// 射线检测
if (Physics.Raycast(fpsCam.transform.position, direction, out RaycastHit rayHit, range))
{
if (rayHit.collider.CompareTag("Wall"))
{
Debug.Log("击中墙壁");
// 击中敌人特效
var res = Instantiate(hitSpecialEffectsWall, rayHit.point, Quaternion.Euler(0, 90, 0));
res.transform.parent = rayHit.transform;//设置父类
}
}
}
//后座力
public class GunRecoil : MonoBehaviour
{
public static GunRecoil Instance;
private void Awake()
{
Instance = this;
}
private Vector2 recoil; // 后坐力向量,存储X和Y方向上的偏移量
public Vector2 addSpeed = new Vector2(0.5f, 0.75f); // 后坐力增加速度
public Vector2 subSpeed = new Vector2(3f, 5f); // 后坐力减少速度
public Vector2 maxRecoil = new Vector2(1, 5); // 后坐力的最大值
void Update()
{
// 使用Mathf.MoveTowards方法逐渐将recoil.x和recoil.y减少到0
// subSpeed.x和subSpeed.y分别表示在每秒内减少的量,乘以Time.deltaTime可以使速度与帧率无关
recoil.x = Mathf.MoveTowards(recoil.x, 0, subSpeed.x * Time.deltaTime);
recoil.y = Mathf.MoveTowards(recoil.y, 0, subSpeed.y * Time.deltaTime);
// 将recoil.y应用到物体的X轴旋转角度,将recoil.x应用到物体的Y轴旋转角度
transform.localEulerAngles = new Vector3(-recoil.y, recoil.x, 0);
}
// 添加后坐力
public void AddRecoil(int xDir)
{
// // 通过随机取值范围[-1, 1]来增加recoil.x的值,并限制在-maxRecoil.x和maxRecoil.x之间
recoil.x = Mathf.Clamp(recoil.x + xDir * addSpeed.x, -maxRecoil.x, maxRecoil.x);
// // 增加recoil.y的值,并限制在0和maxRecoil.y之间,纵向后座力一般只有往上的
recoil.y = Mathf.Clamp(recoil.y + addSpeed.y, 0, maxRecoil.y);
}
}
[System.Serializable]
public struct AmmoPosData
{
public float mirrorRate;//镜像概率
public Data[] datas; // 子弹数量对应的数据
// 根据子弹数量获取子弹偏移方向,和是否镜像
public int GetDir(int ammoCount, bool mirror)
{
int maxId = datas.Length - 1;
int nextId = 0;
Data dt;
// 在数据列表中查找匹配的数据
do
{
dt = datas[nextId];
nextId++;
} while (nextId <= maxId && ammoCount > dt.ammo);
// 根据随机数确定偏移方向
float random = Random.Range(0, 1f);
if (random < dt.left)
{
return mirror ? 1 : -1; // 左偏移
}
else if (random < dt.left + dt.right)
{
return mirror ? -1 : 1; // 右偏移
}
return 0; // 不偏移
}
//按概率判断是否镜像
public bool GetMirror()
{
if(Random.Range(0, 1f) < mirrorRate)
return true;
return false;
}
[System.Serializable]
public struct Data
{
public int ammo; // 子弹数量
public float left; // 左偏移概率
public float right; // 右偏移概率
}
}
【视频】https://www.bilibili.com/video/BV1j44y1S7fX/
赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注
,以便我第一时间收到反馈,你的每一次支持
都是我不断创作的最大动力。当然如果你发现了文章中存在错误
或者有更好的解决方法
,也欢迎评论私信告诉我哦!
好了,我是向宇
,https://xiangyu.blog.csdn.net
一位在小公司默默奋斗的开发者,出于兴趣爱好,最近开始自学unity,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!php是工作,unity是生活!如果你遇到任何问题,也欢迎你评论私信找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~