Making Coroutine Repeat?

I’m currently trying to code a charge shot for a weapon, just like you’d see in a Mega Man game. However, while everything else seems to work just fine for the first shot, the next ones do not. It’s like this: the coroutine I set up works just fine; makes you hold the button down for two seconds to fire off the charged shot. Great. But, after that, the charged shots fire off immediately with each GetButtonUp use. Think semi-auto fire, as fast as you can pull the trigger. Clearly, that won’t do for a charged shot. I believe the issue is with the coroutine. They are only used once per script correct? So, how can I make it to where the coroutine can be used as many times as I want? I tried a while loop with yield, but it doesn’t work. Just makes it fire off four or five charged shots in one button press, still semi-auto. Is it because I didn’t put it in the right place? Or, is it not a good function to use for this altogether? Here is my current script:

public Transform chargeSpawn;
public GameObject chargeShot;
private float chargeTime = 0;
private float chargeRate = 2f;
public float fireRate1;
public float nextFire1;
void Update()
{
    StartCoroutine(TimerRoutine());
}
IEnumerator TimerRoutine()
{
    while (true)
    {
        if (Input.GetButtonDown("Fire1"))
        {
            yield return new WaitForSeconds(2f);
            chargeTime += chargeRate;
        }
        else if (Input.GetButtonUp("Fire1") && Time.time > 2f)
        {
            Instantiate(chargeShot, chargeSpawn.transform.position, chargeSpawn.transform.rotation);
            GetComponent<AudioSource>().Play();
            chargeTime = 0;
        }
        else if (Input.GetButtonUp("Fire1") && Time.time < 2f)
        {
            chargeTime = 0;
        }
        yield return new WaitForSeconds(5f);
    }
}

You are creating a new coroutine every frame. No good.

Instead, give up on the coroutine and make it all happen in the update.

void Update()
 {
         if (Input.GetButton("Fire1"))
         {
             chargeTime += chargeRate;
             if(chargeTime > timerToShoot){ 
                   Shoot();
                   chargeTime = 0f;
         }
         else if (Input.GetButtonUp("Fire1") 
         {
             chargeTime = 0;
         }
 }

Hello, there’s multiple problems, mainly, you have too much stuff, there, so here is stripped version.

public Transform chargeSpawn;
public GameObject chargeShot;
public float chargeRate = 2f; // make this public, maybe give it better name like "chargeTime"

void Start()
{
	StartCoroutine(TimerRoutine());
}

IEnumerator TimerRoutine()
{
	while (true)
	{
		if (Input.GetButtonDown("Fire1"))
		{
			yield return new WaitForSeconds(chargeRate);
		}
		else if (Input.GetButtonUp("Fire1"))
		{
			Instantiate(chargeShot, chargeSpawn.transform.position, chargeSpawn.transform.rotation);
			GetComponent<AudioSource>().Play();
		}

		yield return null; 

	}
}

Also, made my own version, to show another way to approach this, with ton of comments

public Transform chargeSpawn;
public GameObject chargeShot;

public float chargeTime = 2f;
private bool readyToShoot = false;

[ Range (0f, 1f)] // this gives it a nice slider in Inspector, it can still be given any value in scripts
public float chargeLevel;

void Update()
{
	if (Input.GetButtonDown ("Fire1")) {
		// Start charging
		StartCoroutine (Charge());
	}

	if (Input.GetButtonUp ("Fire1") && readyToShoot) {
		// Shoot
		readyToShoot = false;
		chargeLevel = 0;
		Instantiate(chargeShot, chargeSpawn.transform.position, chargeSpawn.transform.rotation);
	}
}

IEnumerator Charge () {
	while (chargeLevel < 1) {
		
		// Track charge level, and use it in other scripts if desired
		chargeLevel += Time.deltaTime / chargeTime;

		if (Input.GetButtonUp ("Fire1")) {
			// fail and quit Charge
			Debug.Log ("Charge failed.");
			chargeLevel = 0;
			yield break;
		}

		// wait here for next frame
		yield return null;
	}

	// Charge succeeded, and we're ready to shoot
	Debug.Log ("Weapon Charged!");
	readyToShoot = true;

	// Also clamp chargeLevel to full = 1
	chargeLevel = 1;
}

Hope this helps!

You can create a bool, canCharge and have it toggle it between shots then you can say that you can’t charge at the beginning of the coroutine then at the end of the timer it should toggle the can charge bool back to true. This should be sufficient to double check everything and enforce the timer. If it doesn’t work you could always drop another coroutine or method timing it all out correctly. I think that just adding the extra bool should be enough though. It would still have to run those sections of the code so adding it might just be enough to make the difference.