Need help fixing shooting in my first 2D game - Unity 6

Hello Unity forums!
This is my first unity project that I’ve undertaken all on my own, and I’m extremely new to Unity, its systems, and C# as a whole. I’ve been researching things I’ve wanted to do and ways to do them, but I’ve found almost nothing helpful that fits what I want to do.

I’m trying to make a 2D dual-stick shooter using Unity 6 and its new Input System Package(which is incredibly complicated to me as a new developer + I hate it). I have movement sorted and working right now, but shooting is where my problem begins. I’ve been struggling with trying to figure out how to continuously spawn bullets after a delay while a key is held. This sounds simple enough to me, but I’ve spent around a week or two testing things and trying to get this to work.

Here’s my code for the player’s shooting:

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

public class PlayerShooting : MonoBehaviour
{
    [SerializeField] private GameObject bulletPrefab;
    [SerializeField] private bool buttonHeld; // for only shooting when button held
    
    private Quaternion bulletRotation; // rotation bullet will face
    private PlayerMovement movementScript; // for playerPos variable from PlayerMovement.cs
    private bool shootBool; // to disable shooting for shot delay or otherwise
    private float shootDelay = 0.5f; // delay in sec between bullets

    void Awake()
    {
        movementScript = GetComponent<PlayerMovement>();
        shootBool = true; // allows player to shoot on game start
    }

    void Update()
    {
        Shoot(bulletRotation); // tries to spawn bullet every frame, only works when buttonHeld true
    }

    public void OnFireUp(InputAction.CallbackContext context)
    {
        if (context.performed && !buttonHeld) // if only up arrow key pressed
        {
            bulletRotation = Quaternion.Euler(0, 0, 0); // bullet faces up
            buttonHeld = true;
        }
        if (context.canceled) // buttonHeld false when key released
        {
            buttonHeld = false;
        }
    }

    public void OnFireRight(InputAction.CallbackContext context)
    {
        if (context.performed && !buttonHeld) // if only right arrow key pressed
        {
            bulletRotation = Quaternion.Euler(0, 0, 270); // bullet faces right
            buttonHeld = true;
        }
        if (context.canceled) // buttonHeld false when key released
        {
            buttonHeld = false;
        }
    }

    public void OnFireDown(InputAction.CallbackContext context)
    {
        if (context.performed && !buttonHeld) // if only down arrow key pressed
        {
            bulletRotation = Quaternion.Euler(0, 0, 180); // bullet faces down
            buttonHeld = true;
        }
        if (context.canceled) // buttonHeld false when key released
        {
            buttonHeld = false;
        }
    }

    public void OnFireLeft(InputAction.CallbackContext context)
    {
        if (context.performed && !buttonHeld) // if only left arrow key pressed
        {
            bulletRotation = Quaternion.Euler(0, 0, 90); // bullet faces left
            buttonHeld = true;
        }
        if (context.canceled) // buttonHeld false when key released
        {
            buttonHeld = false;
        }
    }

    private void Shoot(Quaternion rotation)
    {
        if (shootBool && buttonHeld)
        {
            Instantiate(bulletPrefab, movementScript.playerPos.position, rotation); // spawns bullet with rotation
            StartCoroutine(ShootDelay()); // delays next shot by shootDelay seconds
        }
    }

    private IEnumerator ShootDelay() // for delaying shots
    {
        shootBool = false;
        yield return new WaitForSeconds(shootDelay);
        shootBool = true;
    }
}

I have the Shoot() method in the Update() method and I’m only changing the variables that that method relies on in the action methods. The buttonHeld boolean is what the Shoot() method relies on, as well as the shootBool boolean, but that variable is managed within the ShootDelay() coroutine. The buttonHeld boolean is managed within the action methods(OnFireUp, Down, etc), where it is set to true when the action is performed, and set to false when the action is cancelled. The problem, though, is that this behavior only happens when firing up, and the first bullet spawns after the 0.5 second delay. Firing left, down, or right by pressing the arrow keys doesn’t set buttonHeld to false when releasing the key.

I tried explaining my problem and expectations clearly here, but if any information is needed to help me solve my problem, I will happily provide. My brain is a bit fried from trying to work on this even just to find the problems to report here. I’m trying to just get as much as I can down in order to explain what I need to but I just want to be done for now.
Thank you in advance.

I will not state step by step how to do this as you are new, its an amazing chance to research, I will say this though:
Look into Coroutines, Do while loops, Delays and InvokeRepeating
Through a combination of these your problem will be fixed (a do while loop is basically a loop that runs if a condition is true or false, make sure to have a break out through conditions like if ballsFired = 10; break;

Try if else instead of multiple if statements
//If pressed true; else false;

It seems what you are aiming to do is start firing bullets once conditions are met and keep firing them until conditions are no longer valid. Having said so you may want to abandon doing stuff in the Update () function, as you have other methods already set up. Namely interpreting input by using the Input System. As for firing working with one button, but not the others, one could suspect erroneous/unclear binding. Anyhow in your code you may want to try calling Shoot () from one of the OnFire methods. The following might not be exactly what you need, but should give you an idea of a slightly different approach. You may want to rename a few members for clarity, too. A static coroutine assures bullets are shot only one way at any given time. In the while loop, you could also add a counter, e.g. 10 bullets max or so. It often proves good practice to limit such loops with either time-to-run- or repetition-limiters. But that’s just a bit on the side.


static Coroutine shootDelay = null;

public void OnFireLeft(InputAction.CallbackContext context)
{
    if (context.performed) // if only left arrow key pressed
    {
        Shoot(Quaternion.Euler(0, 0, 90)); // bullet faces left
    }
    if (context.canceled) // buttonHeld false when key released
    {
        buttonHeld = false;
    }
}

private void Shoot(Quaternion rotation)
{
    if ( shootDelay  != null ) { StopCoroutine(shootDelay); }
    shootDelay = StartCoroutine(ShootDelay());
}

private IEnumerator ShootDelay() // for delaying shots
{
    int counter = 0;
    int counterMax = 10;
    buttonHeld = true;
    while(buttonHeld && counter < counterMax)
    {
        counter++;
        Instantiate(bulletPrefab, movementScript.playerPos.position, rotation); // spawns bullet with
        yield return new WaitForSeconds(shootDelay); 
    }
    shootDelay = null;
    yield break;
}