[Unity tips] Ideas for creating recoil in FPS games

Preface

In first-person shooter (FPS) games, controlling the recoil of firearms is to increase the authenticity and challenge of the game. The recoil after shooting usually causes the player's perspective and muzzle to shift, affecting the accuracy of subsequent shots. Here are some common ways to simulate and control recoil:

  1. View angle shift:
    When the player fires a gun, the player's view angle can be programmed to shift upward or sideways to simulate the muzzle jump caused by recoil. This shift is usually instantaneous, and is followed by a recovery process in which the perspective gradually returns to its original position.

    Implementation steps:

    • Listen for shooting events.
    • When shooting, the player's camera rotation is immediately adjusted to offset upward by a certain angle (simulating vertical recoil).
    • If desired, you can also add a random offset in the horizontal direction (simulating horizontal recoil).
    • Gradually restore the camera rotation angle to the state before shooting through an interpolation function (such as Lerp or Slerp).
  2. Firearm animation:
    You can create a recoil animation for the firearm and play this animation when shooting. Animations can be pre-produced in 3D modeling software, or dynamically generated through programs.

    Implementation steps:

    • Design and animate recoil.
    • Triggers the animation to play when shooting.
    • After the animation ends, you can let the gun gradually return to the initial position, or maintain a certain offset during multiple consecutive shots.
  3. Firearm physics simulation:
    Use a physics engine to simulate the recoil effect of firearms. This approach makes recoil more natural and predictable.

    Implementation steps:

    • Attach a physics component (such as a rigid body) to the gun model.
    • When firing, a backward force or impulse is applied to the firearm.
    • The physics engine is used to process the effect of this force, causing the firearm to move backward and upward.

Combining these methods, developers can create recoil effects that meet game design needs, making the shooting experience both challenging and fun. In practical applications, the size and expression of recoil are usually adjusted according to the characteristics of different weapons, so that players can master the shooting characteristics of each weapon through skills and habits.

This article mainly explores the first method, because when shooting firearms, we usually choose ray projection to achieve the recoil effect that best matches the first method.

No recoil effect

For the specific implementation of shooting, please see my previous article:A general FPS gun and different weapon shooting control script
Insert image description here

Simply add recoil

We first make a camera and add a parent class to the camera. The principle of realizing recoil is to control the xy rotation offset value of the parent class of the camera.

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;
    }
}

Effect
Insert image description here

Limit recoil

If we want to be more accurate, we can modify addSpeed ​​and subSpeed ​​to Vector2 values. Of course, we don't want the recoil to cause the viewing angle to keep moving upwards, so we need to limit a value.

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);
}

Effect
Insert image description here

Bullet impact point control

Modify 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; // 右偏移概率
    }
}

Modify GunSystem call and launch bullet script

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));
	
	//。。。
}

1. If we want a 七字型 trajectory, then let the first four bullets go to the right, then the next ten bullets go to the left, and the final interval will be 50-50.

Insert image description here
Effect
Insert image description here

2. If you want a T shape, you can directly modify the configuration.

Insert image description here
Effect

Insert image description here

Mirror ballistics

Modify 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; // 右偏移概率
    }
}

Modify GunSystem call and launch bullet script

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));
	
	//。。。
}

Configure the mirroring probability to 0.5
Insert image description here

The test shows that the ballistic trajectory has half the probability of becoming a mirror image.
Insert image description here

Add scattering

The current trajectory is too regular. The bullet will hit wherever it is pointed. However, in the actual game, the bullet may deviate to a certain extent based on the aiming ray based on the size of the impact point range. The size of the crosshair reflects the impact point laterally. The size of the interval
Insert image description here
actually defines the dispersion during shooting

[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;//设置父类
        }
    }
}

Complete code

//后座力
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; // 右偏移概率
    }
}

grateful

【Video】https://www.bilibili.com/video/BV1j44y1S7fX/

end

Giving roses to others will leave a lingering fragrance in your hands! If the content of the article is helpful to you, please don't be stingy with your 点赞评论和关注 so that I can receive feedback as soon as possible. Every time you 支持 The greatest motivation for creation. Of course, if you find 存在错误 or 更好的解决方法 in the article, you are welcome to comment and send me a private message!

Good, I am向宇,https://xiangyu.blog.csdn.net

A developer who works quietly in a small company has recently started to learn Unity by himself out of hobbies. In his spare time, he records and shares while learning. Standing on the shoulders of giants, learning from the experiences of my predecessors will always give me a lot. Help and inspire! PHP is work, Unity is life! If you encounter any problems, you are also welcome to comment and send me a private message. Although I may not know some of the problems, I will check the information from all parties and try to give the best suggestions. I hope to help more people who want to learn programming. , encourage each other~

Insert image description here

Guess you like

Origin blog.csdn.net/qq_36303853/article/details/134909708