Creating A Progress Bar with New Unity Input system

Simon Pham
4 min readJan 18, 2024

In today’s blog post, we’ll be exploring the creation of a progress bar that charges up when the Space key is held and discharges when the Space key is released.

Setting up the progress bar

Firstly, let’s create an image by right-clicking in the Hierarchy window > UI > Image. Adjust the size and position of the bar to your preference. Since I want my bar to have rounded corners, I’m going to use this as my Source Image. You can easily create one for yourself using a Photo Editing software like Figma or Photoshop.

Progress Bar Source Image

I’m going to use this image as my background and create a nested image as the progress indicator.

Next, let’s change its color to green and use the same image as the Source Image. Also, ensure that Image Type is set to “Filled”, Fill Method is set to “Horizontal”, Fill Origin is set to “Left”, and Fill Amount is 0.

The Image component of the Bar image

Setting up the Action Map

To create a new Input Action asset, right-click on the Project window and select Create > Input Actions. I’m gonna call it “UIInputActions”. Also, let’s create an Action Map called “UI” and an Action called “Charge”. This can simply be a button, and I’ll bind it to the Space key.

Next, select the UIInputActions, check the “Generate C# Class” option, and click Apply to generate a C# script for the Input Action asset that we just created.

Scripting

Let’s create a new script for the Bar. Inside this script, we first need to get a reference to the Input Action asset and enable the Action Map. Since I want the bar to fill up when the Space key is held and empty out when the Space key is released, it’s a good use case for the “started” and “canceled” callback.

using UnityEngine;
using UnityEngine.InputSystem;

public class Bar : MonoBehaviour
{
private UIInputActions _input;

private void Start()
{
_input = new UIInputActions();
_input.UI.Enable();
_input.UI.Charge.started += Charge_started;
_input.UI.Charge.canceled += Charge_canceled;
}

private void Charge_started(InputAction.CallbackContext context)
{
//TODO: Filling up the bar
}

private void Charge_canceled(InputAction.CallbackContext context)
{
//TODO: Emptying the bar
}
}

Next, to fill and empty the bar, we need an indicator that tells Unity if the bar is charging and a coroutine that can increase/decrease the fillAmount property of the Bar image (the bar is full when fillAmount is 1 and empty when it's 0).

Let’s get a reference to the Image component:

private Image _barSprite;

private void Start()
{
_barSprite = GetComponent<Image>();
if( _barSprite == null)
{
Debug.Log("Bar Sprite is null");
}
}

Here’s the coroutine:

private float _chargeAmountPerSec = 0.5f;
private bool _isCharging = false;

IEnumerator ChargeBarRoutine()
{
while (_isCharging == true)
{
_barSprite.fillAmount += _chargeAmountPerSec * Time.deltaTime;
yield return null;
}

while (_barSprite.fillAmount > 0)
{
_barSprite.fillAmount -= _chargeAmountPerSec * Time.deltaTime;
yield return null;
}
}

And here’s the full code:

using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;
using System.Collections;

public class Bar : MonoBehaviour
{
private UIInputActions _input;
private Image _barSprite;
private float _chargeAmountPerSec = 0.5f;
private bool _isCharging = false;

private void Start()
{
_input = new UIInputActions();
_input.UI.Enable();
_input.UI.Charge.started += Charge_started;
_input.UI.Charge.canceled += Charge_canceled;

_barSprite = GetComponent<Image>();
if( _barSprite == null)
{
Debug.Log("Bar Sprite is null");
}
}

private void Charge_started(InputAction.CallbackContext context)
{
_isCharging = true;
StartCoroutine(ChargeBarRoutine());
}

private void Charge_canceled(InputAction.CallbackContext context)
{
_isCharging = false;
StartCoroutine(ChargeBarRoutine());
}

IEnumerator ChargeBarRoutine()
{
while (_isCharging == true)
{
_barSprite.fillAmount += _chargeAmountPerSec * Time.deltaTime;
yield return null;
}

while (_barSprite.fillAmount > 0)
{
_barSprite.fillAmount -= _chargeAmountPerSec * Time.deltaTime;
yield return null;
}
}
}

So the coroutine will increase the fillAmount when the _isCharging is true, and it will decrease the fillAmount when its value is greater than 0 and _isCharging is false. The Charge_started and Charge_canceled functions are used to switch the _isCharging variable’s value. Here’s the result:

--

--