# 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;
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?

• What behavior would you like if they shoot two bullets wait a long time and then try to shoot three bullets? If this is really a cool down then the gun should have cooled by the time they want to shoot the three bullets. If it's a mini clip with three bullets each then waiting would make sense.
– Rick
May 19 '15 at 16:34

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;

}


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
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! :)

• Sure! You're welcome. A small little challenge by the way, if you want to optimize your game. Let's say I'm the player, I shoot 2 times in a row, then I wait 1 full second, then I shoot 1 time. I still have to wait 1 full second again. Don't you think you should let the player reset all his 3 bullets if he waits 1 full second?
– Zee
May 19 '15 at 14:24
• Why have the if (bulletCounter == 3) check outside the keypress handling code? The only way bulletCounter can become 3 is by getting incremented when a shot is fired, so it IMO makes more sense to check for it right there. (For example if (bulletCounter == 3) { gunCooldown = Time.time + 0.80f; bulletCounter = 0; } else { gunCooldown = Time.time + 0.12f; } or even just gunCooldown = Time.time + (bulletCounter % 3 == 0 ? 0.80f : 0.12f );) May 19 '15 at 17:04
• @Zee: If the longer cooldown is meant to simulate reloading a clip, then you might not want to auto-reload until the gun is empty, or unless the player explicitly does an early reload. May 19 '15 at 17:16

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;
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;

• You've identified the issue - but how would he fix it? May 19 '15 at 12:48
• Possible solution in edit :) May 19 '15 at 13:26
• Thanks for the answer - didn't get to try it since I saw the other reply first but some useful theory May 19 '15 at 14:20

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;

//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;

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


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>;
}

//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.

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.