Trying to implement gun cooldowns in C#

Trying to implement gun cooldowns in C#

My aim is to allow my player to shoot 3 bullets at a time. Between shooting these 3 bullets the player should wait a very small interval (so that they don't all get fired at the same time) - call this gunLocalCooldown. After shooting these 3 bullets, the player should wait a much longer interval - call this gunGlobalCooldown. I also have a boolean canShoot that must be true for a bullet to fire (i.e. when the gun is off cooldown).
Here is  the code responsible for this in Update():
// If space is pressed, the gun is off local cooldown and the player can shoot
if (Input.GetKey (KeyCode.Space) && Time.time >= gunLocalCooldown && canShoot) {
    // Bullet prefab
    Rigidbody bPrefab = Instantiate (bulletPrefab, transform.Find ("Bullet").position, Quaternion.identity) as Rigidbody;
    bPrefab.AddForce (transform.up * bulletForce);
    gunLocalCooldown = Time.time + 0.12f;           
    bulletCounter++;
    // If 3 bullets are shot, wait for gunGlobalCooldown then reset counter to 0 and allow him to shoot
    if(bulletCounter == 3) {
        gunGlobalCooldown = gunLocalCooldown + 0.8f;
        if(Time.time >= gunGlobalCooldown) {
            gunGlobalCooldown = Time.time + 0.8f;
            canShoot = true;
        } else {
            canShoot = false;
        }
    bulletCounter = 0;
    }
}

What happens right now: after player shoots 3 bullets he can no longer shoot. The problem is that after canShoot is set to false, it is never set to true again (it should be set to true if the player is able to fire 3 bullets again) - and after playing around with this code for a while I still can't figure out how to do this.
As well as this, I want the player to be able to fire 3 bullets again if he shoots even 1 or 2 bullets as long as the global cooldown is complete. 
Any ideas? 

Solutions/Answers:

Answer 1:

Your code is very scrambled. You have all your time relative variables inside the shooting loop. and the instantiation code too. Why? Why not simply make it like this :

void ShootBullet() {

        Rigidbody bPrefab = Instantiate (bulletPrefab, transform.Find ("Bullet").position, Quaternion.identity) as Rigidbody;
        bPrefab.AddForce (transform.up * bulletForce); 

    }

There, this is the ShootBullet() method, great. So if he presses Space, he shoots, and adds to the shooting counter. If he shot once, then he needs to wait for 0.12f, if he shot 3 times, then he needs to wait for 0.8f . You don’t need to have two variables for this, nor do you need to have both of them inside the Space button loop. Just get both of these variables into one variable, that will control the canShoot variable, that will be the only one deciding if he can shoot or not.

void Update() {

if (Input.GetKey(Keycode.Space) && canShoot) { //if He can shoot, and he pressed space
  ShootBullet(); // then shoot
  bulletCounter++; //add to the counter 
  gunCooldown = Time.time + 0.12f; // set the guncooldown to the small wait value
  canShoot = false;  //he can't shoot anymore
}

if (!canShoot && Time.time > gunCooldown) // once he can't shoot, and enough time passed
   canShoot = true; //he can now shoot

if (bulletCounter == 3) { //once he shot 3 times
gunCooldown = Time.time + 0.8f; // set the guncooldown to the longer wait value
bulletCounter = 0; //reset the bulletcount
}


}

And tadaa, there you have it! Clean and tidy! 🙂

Answer 2:

In the frame that bulletCounter become 3, Time.time will be less than gunGlobalCooldown because this line of code:

gunLocalCooldown = Time.time + 0.12f;     
gunGlobalCooldown = gunLocalCooldown + 0.8f;

so the boolean canShoot will be set to false, and this if

if (Input.GetKey (KeyCode.Space) && Time.time >= gunLocalCooldown && canShoot)

will never be triggered again.

Try simply put the increment of the Cooldown variable under the inner if, something like this:

if (Input.GetKey (KeyCode.Space) && Time.time >= gunLocalCooldown && canShoot) {
// Bullet prefab
Rigidbody bPrefab = Instantiate (bulletPrefab, transform.Find ("Bullet").position, Quaternion.identity) as Rigidbody;
bPrefab.AddForce (transform.up * bulletForce);          
bulletCounter++;
// If 3 bullets are shot, wait for gunGlobalCooldown then reset counter to 0 and allow him to shoot
if(bulletCounter == 3) {
    if(Time.time >= gunGlobalCooldown) {
        gunGlobalCooldown = Time.time + 0.8f;
        canShoot = true;
    } else {
        canShoot = false;
    }`
gunGlobalCooldown = gunLocalCooldown + 0.8f;
bulletCounter = 0;
}
gunLocalCooldown = Time.time + 0.12f;

Answer 3:

So this doesn’t produce exactly the behavior you asked for but I think it might be what you’re looking for anyway.

This code (syntax not exact as I don’t use c#) would allow someone to sustain a fire rate of one bullet per 0.5 seconds indefinitely, but fire bursts of up to 3 bullets at a rate of one bullet per 0.12 seconds.

gunBurst=0.12f
gunSustained=0.5f
gunBurstCount=3
gunSustainedDelay=(gunBurstCount-0.9f)*(gunSustained-gunBurst)


// If space is pressed and the gun is cool
if (Input.GetKey (KeyCode.Space) && Time.time >= gunBurstCooldown && Time.time >= gunSustainedCooldown ) {
    // Bullet prefab
    Rigidbody bPrefab = Instantiate (bulletPrefab, transform.Find ("Bullet").position, Quaternion.identity) as Rigidbody;
    bPrefab.AddForce (transform.up * bulletForce);

    //update cooldowns
    gunBurstCooldown = Time.time + gunBurst;            
    gunSustainedCooldown = Math.Max(gunSustainedCooldown + gunSustainedDelay,Time.time-gunSustainedDelay);
}

This simply prevents firing if the gun is too hot to sustain more fire. If you’d like some of punishment period for trying to fire faster than the sustained fire rate. See below:

//consts
gunBurst=0.12f
gunSustained=0.5f
gunBurstCount=3
gunReset=1.8f
gunSustainedDelay=(gunBurstCount-0.9f)*(gunSustained-gunBurst)


// If space is pressed and the gun is cool
if (Input.GetKey (KeyCode.Space) && Time.time >= gunBurstCooldown &&  Time.time >= gunResetCooldown) {
    if(Time.time >= gunSustainedCooldown){
        // Bullet prefab
        Rigidbody bPrefab = Instantiate (bulletPrefab, transform.Find ("Bullet").position, Quaternion.identity) as Rigidbody;
        bPrefab.AddForce (transform.up * bulletForce);

        //update cooldowns
        gunBurstCooldown = Time.time + gunBurst;            
        gunSustainedCooldown = Math.Max(gunSustainedCooldown + gunSustainedDelay,Time.time-gunSustainedDelay);
    }else{
        gunResetCooldown= Time.time + gunReset;
    }
}

Answer 4:

Coroutines are the best performance-wise. I.e.

void Start ()
{
    //Starting the coroutine
    StartCoroutine (Shooter());
}

IEnumerator Shooter ()
{
    if (Input.GetMouseButton(0))
    {
        //Shoots the bullet
        Rigidbody bullet = Instantiate (Bullet).GetComponent<Rigidbody>;
        bullet.AddForce (Camera.main.transform.forward * 10, ForceMode.VelocityChange);
    }

    //Waits half a second before ticking again
    yield return new WaitForSeconds (.5f);
    StartCoroutine(Shooter());
}

Note that this, like Update() is dependent on framerate. For a smoothly running game, it should be no problem.

Answer 5:

The last time I faced something like this I came at it from a very different direction:

I didn’t use a global cooldown at all. After a shot was fired I set a reloading timer on that barrel. Every tick the reloading timers were decremented. (Watch out for roundoff errors if you’re using floating point–I simply used a certain number of game ticks.) When the user tried to fire I would look for a barrel that wasn’t reloading. If I found one I fired the shot and set the timer for that barrel.

This does mean checking counters for every barrel for every cycle but it was a trivial amount of the CPU power back then and now we have probably 10,000x the CPU that I did when I did this.

References