0
\$\begingroup\$

Hi I'm trying to get my enemy to fire once every few seconds, I tried using a Coroutine but due to me essentially calling the Coroutine through the update method, the Coroutine is repeating every frame update and spamming the shoot key. What is a solution for this?

void Update () {
        Shoot ();
}

public void Shoot(){

        StartCoroutine ("ShootContinuous");

}

public IEnumerator ShootContinuous() {
    yield return new WaitForSeconds (1);
    anim.SetTrigger("Shoot");
    Instantiate (Bullet, firePoint.position, firePoint.rotation);
    isShooting = true;
    yield return new WaitForSeconds (1);
    anim.SetTrigger("NoShoot");
    isShooting = false;

}
\$\endgroup\$
2
\$\begingroup\$

I've found that making a looping script works well for an attack timer, and avoids the problems that come with using a coroutine to time something. Bonus here is that you can use this anywhere in your scripts for an easy timer.

private bool canAttack; //This gets changed when the enemy can try and shoot at you (ie. within range)

private float newFireTime; //This holds the time for the next shot
private float stopFireTime; //Holds the time for when you want to change your anim
private float firingDelay; //The delay in seconds you set (ie a value of "2" means 1 shot every 2 seconds)

private bool attackLoopStarted;

void Update()
{
    if(canAttack)
    {
        AttackTimer();
    }
}

private void AttackTimer()
{
    if (!attackLoopStarted) //If the attack loop has not been initiated
    {
        attackLoopStarted = true; //Signal the loop has been started
        newFireTime = Time.time + (1 / fireRate); //Takes your fire delay in seconds and converts it to shots Per second and record this
        stopFireTime = Time.time + (1 / stopFireTime); // Same as above, except for your animation
        Shoot(); //Call your method to initiate an attack
    }

    if (attackLoopStarted) //Once the attack loop has started check the time against the future time
    {
        //If the gametime is greater than or equal to the newFireTime, then set the bool to false to restart the loop
        if (Time.time >= newFireTime) 
        {
            attackLoopStarted = false;
        }
        //Will only check when isShooting is true, will change the aniamtion after your specified time
        else if (Time.time >= stopFireTime && isShooting == true) 
        {
            StopShoot(); //Call method to change your anim and set the isShooting bool to false
        }
    }
}

private void Shoot()
{
    anim.SetTrigger("Shoot");
    Instantiate(Bullet, firePoint.position, firePoint.rotation);
    isShooting = true;
}

private void StopShoot()
{
    anim.SetTrigger("NoShoot");
    isShooting = false;
}

Edit: This exact code will present a bug if your fire delay, and the delay for your animation are exactly the same (say you want to shoot every 2 seconds and you want the animation to change every 2 seconds at the exact time of the shot, which would probably work against what I believe you are trying to accomplish). You can fix that by replacing stopFireTime = Time.time + (1 / stopFireTime); with stopFireTime = Time.time + (1 / fireRate / 2);that will casue the animation to change back from firing to not firing 1/2 way between shots.

\$\endgroup\$
1
\$\begingroup\$

If you don't want them stacking up, just use a bool for a flag.

bool inShootingLoop = false; //keep track of when the coroutine is running

public void Shoot(){
    if(!inShootingLoop)
        StartCoroutine ("ShootContinuous");
}

public IEnumerator ShootContinuous() {
    inShootingLoop = true; //we're running, set this to make sure we don't start again
    yield return new WaitForSeconds (1);
    anim.SetTrigger("Shoot");
    Instantiate (Bullet, firePoint.position, firePoint.rotation);
    isShooting = true;
    yield return new WaitForSeconds (1);
    anim.SetTrigger("NoShoot");
    isShooting = false;
    inShootingLoop = false; //we're done, set this to allow another iteration
}

Or you can just run your coroutine forever:

public IEnumerator ShootContinuous() {
    while(true) {
        yield return new WaitForSeconds (1);
        anim.SetTrigger("Shoot");
        Instantiate (Bullet, firePoint.position, firePoint.rotation);
        isShooting = true;
        yield return new WaitForSeconds (1);
        anim.SetTrigger("NoShoot");
        isShooting = false;
    }
}
\$\endgroup\$
  • \$\begingroup\$ Why use the coroutine instead of a timer method? I've found that when it comes to fire-rate, coroutines do not provide always accurate results. If you go within firing range and out of firing range in between coroutines the enemies firerate may not always match up to exactly 1 second and you get off behaviors. This can be solved with some unnecessarily complicated logic that ends up being a timer outside of the coroutine anyways. \$\endgroup\$ – Douglas Gaskell May 2 '15 at 2:33
  • \$\begingroup\$ @douglas14b This is just using OP's existing code. They may have their own reasons for using a coroutine. You can post an answer with your strategy, I'd vote for it \$\endgroup\$ – MichaelHouse May 2 '15 at 2:44
  • \$\begingroup\$ Ah, I see, added my 2 cents. \$\endgroup\$ – Douglas Gaskell May 2 '15 at 3:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.