Using coroutines to check if player interrupted action

I am making a FPS game and I noticed that the way it was done, the player could reload and immediately equip another weapon to cancel the reload animation and delay. I tried to fix this using IEnumerators, and it seems to work sort of fine, but I feel like I am doing something wrong…

Will this code cause performance issues? Would it be better to use void Update () for this?

    void StartReload ()
    {
        if (TotalAmmo > 0)
        {
            if (AnimatorController != null)
            {
                AnimatorController.CrossFadeInFixedTime(ReloadAnimationName, 0f);
            }
            ReloadSound.Play();

            AnimatorController.SetLayerWeight(1, 0); //Sets the Slide Lock Back in place when reloading//

            StartCoroutine(Reload());

            StartCoroutine(TryStopReload(true));
        }
        else
        {
            NoAmmoSound.Play ();
        }
    }

    IEnumerator TryStopReload (bool Continue)
    {
        yield return new WaitForSeconds(0.1f);

        if (!Equipped)
        {
            StopCoroutine(Reload());
        }

        if (Continue)
        {
            StartCoroutine(TryStopReload(true));
        }
        else
        {
            yield break;
        }
    }

    IEnumerator Reload ()
    {
        yield return new WaitForSeconds(ReloadDelay);

        if (SingleBulletReload)
        {
            TotalAmmo--;
            Ammo++;
        }
        else
        {
            int ReloadingAmount = AmmoCapacity - Ammo;

            if (TotalAmmo > ReloadingAmount)
            {
                TotalAmmo -= ReloadingAmount;
                Ammo += ReloadingAmount;
            }
            else
            {
                TotalAmmo = 0;
                Ammo += TotalAmmo;
            }
        }

        CurrentFireDelay = ReloadDelay;

        if (HUD != null)
        {
            HUD.AmmoCounter.text = Ammo + "/" + TotalAmmo;
        }

        StartCoroutine(TryStopReload(false));
    }

Explanation:
Basically, the first method void StartReload () starts the reload animation, and calls both the other methods, which are coroutines. The method IEnumerator Reload () changes the number of bullets in the player’s weapon, so it’s only called after all the delays to make sure player can’t skip this by changing weapons. The method IEnumerator TryStopReload (bool Continue) tries to interrupt the call to IEnumerator Reload () if it sees that the player tried to equip another weapon, cancelling the reload and forcing the player to reload again. IEnumerator TryStopReload (bool Continue) is called every tenth of a second (using a normal void method causes a stack overflow, hence it is a coroutine and is only called once per tenth of a second) until IEnumerator Reload () is finally called, cancelling it.

I feel like there is something wrong with this approach, either it’s performant heavy, or it’s just not optimal for some reason… Maybe I should just use something with void Update ().

To me this is not a coroutines problem.

Any problem that involves calling StopCoroutine() is almost certainly not suited to coroutines.

Just use flags and timers to keep track of what is going on.

You also have to consider what you expect when the user switches weapons:

  • prohibit switching while reloading?
  • allow switch and let the reloading continue in the background on the other weapon?
  • allow switch and instant switch back?
  • allow switch but track how far reloaded you are if you switch back?
  • allow switch but force a complete restart of reload if you switch back?

That makes you think of things like, does each weapon need to track its own notion of how far reloaded it is? Can a weapon be partially reloaded (eg only a few of the bullets like in a revolver)? etc.

StartCoroutine returns a Coroutine object that allows you to track the running coroutine. You can save yourself a lot of headache and silly workarounds by utilizing it.

Coroutine _reloading;

public bool reloading { get { return _reloading != null; } }

void Reload()
{
   if(reloading == false) // don't double reload
       _reloading = StartCoroutine(ReloadCoroutine());
}

void Unequip()
{
    if(reloading == true)
   {
       StopCoroutine(_reloading);

       _reloading = null;
   }
}

void ReloadCoroutine()
{
   // yield until the time is up
   // add the bullets

   _reloading = null;
}

Why?

How would that look like?

I expect the last option (allow switch but force a complete restart of reload if you switch back).

No. The only weapon that is handle differently are shotguns that are loaded one by one, but they should still work the same way as normal weapons, except their reload delay is much shorter and they only put in 1 bullet per reload. If you interrupt a shotgun reload, you should still not be able to put the bullet in.

After last bullet fired:

  • set reload flag
  • set timer for reloading

If reloading is in progress:

  • don’t allow shooting
  • count down timer
  • when timer reaches zero, clear reload flag.

That’s it.

If you have multiple weapons, then each weapon has its own notion of the above flag and timer.

You can even combine (conflate) the nonzero-ness of the timer and do away with the boolean. In fact, that’s how I’d do it. I’d just make the bool a get-only property that returns true if the timer is greater than zero.

1 Like