How to make a gun continuously fire by holding down a trigger?

So I am using Unity’s new Input system to handle shooting my gun, I will be using a Gamepad.
The issue I have is, I can only tap the “Shoot” input (Right Trigger) to fire the gun.
I want to be able to hold down the trigger and shoot so it’s like an assault rifle.
I then want it to instantly stop shooting once I release the “Shoot” input.

How do I achieve this? This is my code…

using UnityEngine;
using TMPro;
using UnityEngine.InputSystem;
using System.Collections;

public class Gun : MonoBehaviour
{
    public float damage = 10f;
    public float range = 100f;
    public float fireRate = 15f;
    public float raycastOffset = 0.5f;
    public float maxAmmo = 30f;
    public float reservedAmmo = 120f;
    public float reloadTime = 2f;
    public TMP_Text ammoText;

    private Camera fpsCam;
    private float nextTimeToFire = 0f;
    private float currentAmmo;

    private PlayerControls controls;
    private bool canShoot = true;

    void Awake()
    {
        controls = new PlayerControls();
        controls.Player.Shoot.performed += ctx => Shoot();
        controls.Player.Reload.performed += ctx => StartCoroutine(ReloadWithDelay());
    }

    void OnEnable()
    {
        controls.Player.Enable();
    }

    void OnDisable()
    {
        controls.Player.Disable();
    }

    void Start()
    {
        fpsCam = Camera.main; 
        currentAmmo = maxAmmo; 
        UpdateAmmoText();
    }

    // Update is called once per frame
    void Update()
    {
        if (Time.time >= nextTimeToFire && currentAmmo > 0)
        {
            if (controls.Player.Shoot.triggered)
            {
                nextTimeToFire = Time.time + 1f / fireRate;
                Shoot();
                currentAmmo--; // Decrease ammo count after shooting
                UpdateAmmoText();
            }
        }

        // Draw debug ray
        Debug.DrawRay(fpsCam.transform.position + fpsCam.transform.forward * raycastOffset, fpsCam.transform.forward * range, Color.red);

        // Disable shoot input when currentAmmo reaches 0
        if (currentAmmo <= 0)
        {
            controls.Player.Shoot.Disable();
        }
        else
        {
            controls.Player.Shoot.Enable();
        }
    }

    void Shoot()
    {
        RaycastHit hit;
        if (Physics.Raycast(fpsCam.transform.position + fpsCam.transform.forward * raycastOffset, fpsCam.transform.forward, out hit, range))
        {
            Debug.Log(hit.transform.name);

            Target target = hit.transform.GetComponent<Target>();
            if (target != null)
            {
                target.TakeDamage(damage);
            }
        }
    }

    IEnumerator ReloadWithDelay()
    {
        yield return new WaitForSeconds(reloadTime);

        if (currentAmmo < maxAmmo && reservedAmmo > 0)
        {
            int ammoNeeded = (int)(maxAmmo - currentAmmo);
            int ammoToReload = Mathf.Min(ammoNeeded, (int)reservedAmmo);
            currentAmmo += ammoToReload;
            reservedAmmo -= ammoToReload;
            UpdateAmmoText();

            if (currentAmmo > 0)
            {
                canShoot = true; // Enable shooting after reload
            }
        }
    }

    void UpdateAmmoText()
    {
        if (ammoText != null)
        {
            ammoText.text = currentAmmo.ToString("00") + " | " + reservedAmmo.ToString("00");
        }
    }

}

I doubt you’ll get any benefit from using the new input system in a fast paced shooting game. I think it was designed more for casual mobile games where the user hits a button now and then and the display is updated occasionally.

Here’s how to do your thing on the old input system:

using UnityEngine;
public class Gun : MonoBehaviour
{
    public float damage=10;
    public float range=100;
    public float fireRate=15;
    public int maxAmmo=30;
    public float reloadTime=2f;
    public int reservedAmmo=120;

    Camera fpsCam;
    float nextShootTime;
    int currentAmmo;

    void Start()
    {
        fpsCam=Camera.main;
        currentAmmo=maxAmmo;
    }

    void Update()
    {  
        if (Input.GetButton("Fire1"))
            Shoot();
        else if (Input.GetKeyDown(KeyCode.R))
            Reload();
    }

    void Shoot()
    {
        if (Time.time<nextShootTime || currentAmmo<1)
            return;
        nextShootTime=Time.time+1f/fireRate;
        if (currentAmmo>0 && Physics.Raycast(fpsCam.transform.position + fpsCam.transform.forward * 0.5f, fpsCam.transform.forward, out RaycastHit hit, range))
        {
            Target target = hit.transform.GetComponent<Target>();
            if (target != null)
                target.TakeDamage(damage);
        }
        currentAmmo-=1;
    }

    void Reload()
    {
        int ammoTaken=Mathf.Clamp(reservedAmmo,0,maxAmmo-currentAmmo);
        currentAmmo+=ammoTaken;
        reservedAmmo-=ammoTaken;
        nextShootTime=Time.time+reloadTime;
    }
}

You can do it with the new input system. You just need to poll if the input is pressed in Update, same as you would with the old input system.

1 Like

Or if you want to honor the new input system’s hopes of reducing polling then when the trigger is pressed you could start a coroutine that spams bullets, and stop the coroutine when the trigger is released. But in a fast paced game this level of optimization won’t really give any noticeable benefit.

Even if targeting mobile it’s not like you’re going to save battery life or reduce overheating in a shooting game where input uses only 0.001% of your game’s total processing time.

I don’t know why people think this. The new input system isn’t about removing the need for polling input. It’s more above providing an alternative, while still allowing you to poll inputs where it’s necessary. Sometimes you want to poll, sometimes all you need is a callback. Using both is more useful than one or the other.

2 Likes

As for the code:

  1. remove redundant activation of Shoot(), currently you are doing both polling the shoot action in update, and registering a callback. Remove the shoot callback registration from Awake. It is fine to keep the callback for reload.

  2. Instead of using action.triggered you should probably use action.IsPressed() . triggered is an alias for WasPerformedThisFrame() , when the action is “performed” will depend on how you configured it and in many cases it will return true only on the single frame action gets “performed”. But for something like continuous shooting with your own repeat timer IsPressed is simpler.

2 Likes