Today on day 18, we’re going to start adding UI components into our game!
2 days ago, on day 16, we created a reload system and just yesterday we started shooting lasers at enemies, now would be the perfect time to create a reload system and some UI to display our reload.
While we’re at it, we might as well make some other UI fixes. Here are the things that we’re going to tackle today:
- Create the reload system and UI
The first thing that needs to be done is to create the reload system.
Without any delays, let’s get started!
Creating the Reload System
There are 2 things that need to be done for us to have a reload system. The first part is the UI that shows how many ammos we have and the second is the code that will manage it.
Let’s create the UI system first.
Player Ammo UI
First thing to do, let’s find a motivation for our UI.
For my source of inspiration, I’m going to use Overwatch
, which I think is pretty common these days:
The basic thing is that the HP is on the bottom left corner and the ammo is on the bottom right.
Let’s get started creating the ammo UI. In the hierarchy pane, right click and under UI, select Text. We’re going to call it Ammo.
As you might recall, when we do this, Unity automatically creates a Canvas
screen for us. In this case, we already have one that called HUD.
To work with our UI, make sure to hit 2D button right below the Scene tab.
Let’s adjust the text to be at the bottom right corner of our screen.
Select Ammo and under the Rect Transform component, select the anchor presets square and while holding Shift + Alt, select bottom right, to move our text to the bottom right corner.
It’s also kind of small, so let’s change the size a bit too:
- Width: 160
- Height: 60
- Text Size: 24
- Font Style: Bold
- Text: 30/30
- Color: White
Here’s what we’ll see now if we pull up our game tab:
Now that we have our initial setup, let’s write code!
Ammo and Reload Code
We have the UI, now it’s time to write some code for reloading and ammo!
We’ll be changing our PlayerShootingController
, here’s the code:
using UnityEngine;
using System.Collections;
public class PlayerShootingController : MonoBehaviour
{
public float Range = 100;
public float ShootingDelay = 0.1f;
public AudioClip ShotSfxClips;
public Transform GunEndPoint;
public float MaxAmmo = 10f;
private Camera _camera;
private ParticleSystem _particle;
private LayerMask _shootableMask;
private float _timer;
private AudioSource _audioSource;
private Animator _animator;
private bool _isShooting;
private bool _isReloading;
private LineRenderer _lineRenderer;
private float _currentAmmo;
void Start () {
_camera = Camera.main;
_particle = GetComponentInChildren<ParticleSystem>();
Cursor.lockState = CursorLockMode.Locked;
_shootableMask = LayerMask.GetMask("Shootable");
_timer = 0;
SetupSound();
_animator = GetComponent<Animator>();
_isShooting = false;
_isReloading = false;
_lineRenderer = GetComponent<LineRenderer>();
_currentAmmo = MaxAmmo;
}
void Update ()
{
_timer += Time.deltaTime;
Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));
Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);
if (Input.GetMouseButton(0) &&
_timer >= ShootingDelay && !_isReloading && _currentAmmo > 0)
{
Shoot();
if (!_isShooting)
{
TriggerShootingAnimation();
}
}
else if (!Input.GetMouseButton(0))
{
StopShooting();
if (_isShooting)
{
TriggerShootingAnimation();
}
}
if (Input.GetKeyDown(KeyCode.R))
{
StartReloading();
}
}
private void StartReloading()
{
_animator.SetTrigger("DoReload");
StopShooting();
_isShooting = false;
_isReloading = true;
}
private void TriggerShootingAnimation()
{
_isShooting = !_isShooting;
_animator.SetTrigger("Shoot");
}
private void StopShooting()
{
_audioSource.Stop();
_particle.Stop();
}
private void Shoot()
{
_timer = 0;
Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit = new RaycastHit();
_audioSource.Play();
_particle.Play();
_currentAmmo--;
_lineRenderer.SetPosition(0, GunEndPoint.position);
StartCoroutine(FireLine());
if (Physics.Raycast(ray, out hit, Range, _shootableMask))
{
print("hit " + hit.collider.gameObject);
_lineRenderer.SetPosition(1, hit.point);
EnemyHealth health = hit.collider.GetComponent<EnemyHealth>();
EnemyMovement enemyMovement = hit.collider.GetComponent<EnemyMovement>();
if (enemyMovement != null)
{
enemyMovement.KnockBack();
}
if (health != null)
{
health.TakeDamage(1);
}
}
else
{
_lineRenderer.SetPosition(1, ray.GetPoint(Range));
}
}
private IEnumerator FireLine()
{
_lineRenderer.enabled = true;
yield return ShootingDelay - 0.05f;
_lineRenderer.enabled = false;
}
public void ReloadFinish()
{
_isReloading = false;
_currentAmmo = MaxAmmo;
}
private void SetupSound()
{
_audioSource = gameObject.AddComponent<AudioSource>();
_audioSource.volume = 0.2f;
_audioSource.clip = ShotSfxClips;
}
}
The code flow for this is straightforward:
- We made 2 new variables
MaxAmmo
and _currentAmmo
to represent how many bullets we have left to shoot. - In
Start()
, we initialize our current ammo amount. - Whenever we shoot, we make sure that our ammo is above
0
, otherwise we can’t shoot and then we decrement our _currentAmmo
count. - When we finish reloading, we’ll restore our ammo to max.
With the code, we have one problem, while we’re shooting, if we never let go of our mouse, we’ll continue to play the shooting animation and sound effect. We need to change this.
I fixed the problem by adding another check to make sure that we stop shooting when we either let go of our mouse or when we run out of bullets.
Here’s the change we did for Update()
:
void Update ()
{
_timer += Time.deltaTime;
Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));
Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);
if (Input.GetMouseButton(0) &&
_timer >= ShootingDelay && !_isReloading && _currentAmmo > 0)
{
Shoot();
if (!_isShooting)
{
TriggerShootingAnimation();
}
}
else if (!Input.GetMouseButton(0) || _currentAmmo <= 0)
{
StopShooting();
if (_isShooting)
{
TriggerShootingAnimation();
}
}
if (Input.GetKeyDown(KeyCode.R))
{
StartReloading();
}
}
Now that we have the script for reloading and shooting, we need to add our shooting mechanisms.
The first thing we’re going to do is to go to our original HUD component that we created in Day 10 and create a new script called ScreenManager
.
The script will be used for ammo and later score count, and in the future health. Here’s our code:
using UnityEngine;
using UnityEngine.UI;
public class ScreenManager : MonoBehaviour
{
public Text AmmoText;
void Start()
{
{
PlayerShootingController shootingController =
Camera.main.GetComponentInChildren<PlayerShootingController>();
UpdateAmmoText(shootingController.MaxAmmo, shootingController.MaxAmmo);
}
}
public void UpdateAmmoText(float currentAmmo, float maxAmmo)
{
AmmoText.text = currentAmmo + "/" + maxAmmo;
}
}
Here’s how our code works:
- We take in the
Text
game object that we use for ammo. - In
Start()
, we initialize our text ammo by getting our PlayerShootingController
that’s a child of our camera and using the max ammo value we set. - Inside
UpdateAmmoText()
, we give it the ammo amount to print out. I made this public
, because we want to call this function from elsewhere.
There was a design decision that I was thinking of as I was going through this.
I was looking back at the score UI that was made for the Survival Shooter tutorial and it used static
variables to represent the score. Static
meaning you can access is anywhere, anytime without needing access to the script itself.
That worked in the context that only the manager needed to know anything about the score, however in our case, we already have our ammo amount in our PlayerShootingController
, so if that’s the case, there’s no need to keep a separate instance to keep track of our ammo amount.
Instead, let’s just pass in the values that we want the text to print out.
Another benefit of this is that we don’t have to re-render our text every single time we call Update()
and we only change it when we need to.
The only downside is that we must get an instance of ScreenManager
whenever we want to make any changes as opposed to the static
method that was used in the tutorial.
Updating our PlayerShootingController
, here’s what we get:
using UnityEngine;
using System.Collections;
public class PlayerShootingController : MonoBehaviour
{
public float Range = 100;
public float ShootingDelay = 0.1f;
public AudioClip ShotSfxClips;
public Transform GunEndPoint;
public float MaxAmmo = 10f;
private Camera _camera;
private ParticleSystem _particle;
private LayerMask _shootableMask;
private float _timer;
private AudioSource _audioSource;
private Animator _animator;
private bool _isShooting;
private bool _isReloading;
private LineRenderer _lineRenderer;
private float _currentAmmo;
private ScreenManager _screenManager;
void Start () {
_camera = Camera.main;
_particle = GetComponentInChildren<ParticleSystem>();
Cursor.lockState = CursorLockMode.Locked;
_shootableMask = LayerMask.GetMask("Shootable");
_timer = 0;
SetupSound();
_animator = GetComponent<Animator>();
_isShooting = false;
_isReloading = false;
_lineRenderer = GetComponent<LineRenderer>();
_currentAmmo = MaxAmmo;
_screenManager = GameObject.FindWithTag("ScreenManager").GetComponent<ScreenManager>();
}
void Update ()
{
_timer += Time.deltaTime;
Vector3 lineOrigin = _camera.ViewportToWorldPoint(new Vector3(0.5f, 0.5f, 0.0f));
Debug.DrawRay(lineOrigin, _camera.transform.forward * Range, Color.green);
if (Input.GetMouseButton(0) &&
_timer >= ShootingDelay && !_isReloading && _currentAmmo > 0)
{
Shoot();
if (!_isShooting)
{
TriggerShootingAnimation();
}
}
else if (!Input.GetMouseButton(0) || _currentAmmo <= 0)
{
StopShooting();
if (_isShooting)
{
TriggerShootingAnimation();
}
}
if (Input.GetKeyDown(KeyCode.R))
{
StartReloading();
}
}
private void StartReloading()
{
_animator.SetTrigger("DoReload");
StopShooting();
_isShooting = false;
_isReloading = true;
}
private void TriggerShootingAnimation()
{
_isShooting = !_isShooting;
_animator.SetTrigger("Shoot");
}
private void StopShooting()
{
_audioSource.Stop();
_particle.Stop();
}
private void Shoot()
{
_timer = 0;
Ray ray = _camera.ScreenPointToRay(Input.mousePosition);
RaycastHit hit = new RaycastHit();
_audioSource.Play();
_particle.Play();
_currentAmmo--;
_screenManager.UpdateAmmoText(_currentAmmo, MaxAmmo);
_lineRenderer.SetPosition(0, GunEndPoint.position);
StartCoroutine(FireLine());
if (Physics.Raycast(ray, out hit, Range, _shootableMask))
{
print("hit " + hit.collider.gameObject);
_lineRenderer.SetPosition(1, hit.point);
EnemyHealth health = hit.collider.GetComponent<EnemyHealth>();
EnemyMovement enemyMovement = hit.collider.GetComponent<EnemyMovement>();
if (enemyMovement != null)
{
enemyMovement.KnockBack();
}
if (health != null)
{
health.TakeDamage(1);
}
}
else
{
_lineRenderer.SetPosition(1, ray.GetPoint(Range));
}
}
private IEnumerator FireLine()
{
_lineRenderer.enabled = true;
yield return ShootingDelay - 0.05f;
_lineRenderer.enabled = false;
}
public void ReloadFinish()
{
_isReloading = false;
_currentAmmo = MaxAmmo;
_screenManager.UpdateAmmoText(_currentAmmo, MaxAmmo);
}
private void SetupSound()
{
_audioSource = gameObject.AddComponent<AudioSource>();
_audioSource.volume = 0.2f;
_audioSource.clip = ShotSfxClips;
}
}
Here’s what we did:
- We looked for our
ScreenManager
by looking for it via a tag
we set on it called ScreenManager
. - Any time we change our ammo amount, we would call
UpdateAmmoText()
in our _screenManager
. In this case, there are 2 places: after we shoot and after we reload.
Now before we try our game, first go to our HUD game object that we attached our ScreenManager
script to, and create a new Tag
called ScreenManager
and make that the Tag
for HUD.
If we were to play the game now, you’ll see that when we shoot, our ammo goes down, and when we hit R to reload, it goes back up to our maximum amount!
Conclusion
That’s it for today! I thought I would get to do more, but it looks like that was not the case for me!
To recap everything we did today, we created an ammo system where after we shoot all of our bullets, we have to reload.
Afterwards, we create a new Text that represents our ammo count on the bottom right hand corner of the screen.
I think tomorrow on Day 19, we’ll continue to add more pieces of the game like player health and a scoring system.
Day 17 | 100 Days of VR | Day 19
Home
CodeProject
The post Day 18: Creating Weapon Ammo in Unity appeared first on Coding Chronicles.