# How to create a refill system over time

I am making an energy beam firing system from a drone. In my code, I have setup a system in which the player presses and holds onto the firing button, then a countdown bar begins to countdown, and when it is empty(down to 0), the energy beam stops firing, then the button waits for sometime before it can be pressed again. All of these works fine but, my problem is: Assuming the countdown bar is not yet empty(maybe half way through), it gradually refills itself to maximum. I've tried to work on it, but I keep getting glitches on the countdown bar. How do I go about this?

EDIT: While the player presses and holds the fire button, the countdown bar begins to count down, then when the player let's go of the button about halfway through the countdown, the countdown bar gradually fills up as expected, now before the countdown bar is filled, if the player presses the fire button again, the countdown bar quickly increases(*here is the glitch) then resumes countdown again.

Here is my code:

[SerializeField] private float EnergyBeamPowerOffTime = 14; //Get this value from the hanger
[SerializeField] private float TimeToRefillEnergy = 10f;
[SerializeField] private GameObject EnergyBeamCountdown;
private Slider EnergyBeamCountdownBar;
[SerializeField] private Image EnergyBeamButtonImage;

public float EnergyBeamFillRate; //This value should be handled when the player upgrades their ships

private float EnergyBeamActiveTime;
private float EnergyRefillRate;

private bool EnergyCoroutinePaused;
private bool FiringEnergyBeam;
private bool CoolEnergyBeamDown;
private bool CoolEnergyBeamButtonDown;
private bool RefillEnergy;

private void Update()
{
EnergyBeamSetting();
}

public void OnDownShootEnergyBeam()     //Let's the user click and hold on this button
{
//PlaceHolders.energyBeamFiringSFX.Play();      //Play the sound effects
droneBrain.OnDownShootEnergyBeam();     //Fire beams towards a target returned by a raycstHit(Irrelevant to this code logic)
FiringEnergyBeam = true;        //Lets the countdown bar know that we started firing energy beams
StartCoroutine(EnergyBeamRoutine());        //Begins the energybar coroutine
}

public void OnUpShootEnergyBeam()       //Called when the user lets go off the button
{
droneBrain.OnUpShootEnergyBeam();   //Stops firing energy beams(Irrelevant to this code logic)
FiringEnergyBeam = false;       //Lets the countdown bar know that we started firing energy beams
if (EnergyBeamActiveTime > 0f)
{
EnergyBeamButtonImage.raycastTarget = true;     //Allow this button to be pressed again
EnergyCoroutinePaused = true;       //Pause coroutine execution
} else
EnergyCoroutinePaused = false;      //Resume coroutine execution
ObjectivesChecker.NumberOfTimesEnergyBeamWasFired++;    //Increase the number of times the energybeam was fired(Irrelevant to this code logic)
}

public void OnUpShootEnergybeam()       //Called when the user lets go off the button
{
RefillEnergy = true;        //Begin to refill energy bar
}

private void RefillEnergySettings()
{
if (RefillEnergy && !FiringEnergyBeam)
{
EnergyRefillRate = 0f;      //Resets the rate of energy beam's refill
EnergyRefillRate += Time.deltaTime;
float Percent = EnergyRefillRate / TimeToRefillEnergy;
EnergyBeamCountdownBar.value = Mathf.Lerp(EnergyBeamCountdownBar.value, 1, Percent);
//EnergyBeamActiveTime = Mathf.Clamp(EnergyBeamActiveTime, EnergyBeamCountdownBar.value, 0);
EnergyBeamActiveTime = Mathf.Lerp(EnergyBeamCountdownBar.value, 0, Percent);
if (EnergyBeamCountdownBar.value >= 1)
RefillEnergy = false;
}
}

private IEnumerator EnergyBeamRoutine()
{
CoolEnergyBeamDown = true;      //Lets the countdown(Energy remaining in the energy beam) begin countdown
EnergyBeamButtonImage.raycastTarget = false;    //Won't allow Energybeam button to be pressed
if(EnergyBeamActiveTime > EnergyBeamPowerOffTime && !EnergyCoroutinePaused)
{
EnergyBeamActiveTime = 0f;
}
EnergyBeamCountdown.gameObject.SetActive(true);     //Makes the countdown visible
yield return new WaitWhile(() => CoolEnergyBeamDown);   //Waits untill CoolEnergyBeamDown is false
//Here, the beam has finished firing, we deactivate it then load the button
EnergyBeamCountdown.gameObject.SetActive(false);    //Makes the countdown invisible
droneBrain.OnUpShootEnergyBeam();   //stops firing energy beam
EnergyBeamButtonImage.fillAmount = 0f;  //resets the energybeam button's inactivity timer
CoolEnergyBeamButtonDown = true;    //begins to prepare the energybeam button to fire again
}

private void EnergyBeamSetting()
{
if (CoolEnergyBeamDown && FiringEnergyBeam)     //Conditions for the countdown bar
{
//Countdown(Energy beam's energy)
EnergyBeamActiveTime += Time.deltaTime;
float Percent = EnergyBeamActiveTime / EnergyBeamPowerOffTime;
EnergyBeamCountdownBar.value = Mathf.Lerp(1, 0, Percent);
if (EnergyBeamCountdownBar.value <= 0)
CoolEnergyBeamDown = false;
}

if (CoolEnergyBeamButtonDown)       //Wait time conditions before the button can be used again
{
//Countdown for the energybeam button to prepare next fire
EnergyBeamButtonImage.fillAmount += 1f / DroneBrain.EnergyWaitTime * Time.deltaTime;
if (EnergyBeamButtonImage.fillAmount >= 1f)
{
EnergyBeamButtonImage.fillAmount = 1f;
CoolEnergyBeamButtonDown = false;
EnergyBeamButtonImage.raycastTarget = true;
EnergyBeamActiveTime = 0f;
}
else
EnergyBeamButtonImage.raycastTarget = false;
}

RefillEnergySettings();
}

• What specific glitch are you experiencing with this code? The more specific you are about the problem you're facing, the more accurate the solutions you'll tend to get, faster. Dec 30 '19 at 9:46
• While the player presses and holds the fire button, the countdown bar begins to count down, then when the player let's go of the button about halfway through the countdown, the countdown bar gradually fills up as expected, now before the countdown bar is filled, if the player presses the fire button again, the countdown bar quickly increases(*here is the glitch) then resumes countdown again. Dec 30 '19 at 9:57

I'm sorry, but the script in the question tries to do so many things at once in such a convoluted way that I gave up trying to understand how it works. So I decided to just rewrite it from scratch.

This script simply manages the value Charge between 0.0f and 1.0f. When the player presses space, the charge gets depleted and the property IsFiring is true, otherwise IsFiring is false and the charge recovers. When the charge ever reaches zero, then the script enters an IsOverheating state where firing isn't possible. These three variables Charge, IsFiring and IsOverheating are exposed as public properties, so they can be used by other scripts.

using UnityEngine;

public class Recharge : MonoBehaviour {
// values to set up in inspector (with reasonable defaults)
[SerializeField]
private float dechargeRate = 0.6f; // decharge per second when the player presses space
[SerializeField]
private float rechargeRate = 0.4f; // recharge per second when the player releases space
[SerializeField]
private float recoveryTime = 1.0f; // time in seconds until firing is possible after the bar was completely depletedafter the bar was completely depleted
// exposed properties to be used by other classes
public float Charge { get; private set; }
public bool IsFiring { get; private set; }
public bool IsOverheating { get => recoveryTimeLeft > 0.0f; }
// internal state
private float recoveryTimeLeft = 0.0f;

void Update() {
// check if the conditions for firing are met
if (Input.GetKey(KeyCode.Space) && recoveryTimeLeft <= 0.0f && Charge > 0.0f) {
// if yes we are firing
IsFiring = true;
Charge -= dechargeRate * Time.deltaTime;
} else {
// if no we are recovering
IsFiring = false;
Charge += rechargeRate * Time.deltaTime;
recoveryTimeLeft -= Time.deltaTime;
}
// check for over-depletion
if (Charge < 0.0f) {
recoveryTimeLeft = recoveryTime;
}
// do not allow charge or recovery time to get negative or larger than 1.0
Charge = Mathf.Clamp01(Charge);
recoveryTimeLeft = Mathf.Clamp(recoveryTimeLeft, 0.0f, recoveryTime);
}
}


But now you might wonder "Where did the UI stuff go?".

I moved this to a separate script. It is always a good idea to decouple your game mechanics from your visualization. This keeps your code easier to read and makes it easier to change one without having to also change the other.

This script requires that you assign an Image to control and a Recharge component to obtain the current state from. It visualizes the current shooting state with the color. Green for ready to fire, Blue for firing and Red for overheat.

using UnityEngine;
using UnityEngine.UI;

public class RechargeUi : MonoBehaviour
{
public Recharge recharge;
public Image rechargeBar;

void Update()
{
rechargeBar.fillAmount = recharge.Charge;

if (recharge.IsFiring) {
rechargeBar.color = Color.blue;
} else if ( recharge.IsOverheating ) {
rechargeBar.color = Color.red;
} else {
rechargeBar.color = Color.green;
}
}
}


If you have any other UI-related stuff which is supposed to depend on the current recharge state (the script in the question mentions a "EnergyBeamButtonImage", for example), this should likely also go here.

But if you have any game mechanics which depend on the recharge mechanics (like actually damage objects in the game) those should go into a separate script.

• This works, I'll reply again if I still encounter any problem. And BTW, that's a really good advice on how to organize code, am not a "professional" coder but I'll improve. Thanks a lot! Dec 31 '19 at 9:41