So far in our game it’s a pretty easy thing to just spam the fire button and fly around with a wall of laser blasts in front of you. So, it’s time to take that away and limit our ammo.
Adding this in is basically trivial. In our Canvas and UI Manager script we can simply copy our existing Score text field and even our Score public method.
[SerializeField] private TMP_Text scoreText;
[SerializeField] private TMP_Text ammoText;
public void NewScore(int score)
{ scoreText.text = "Score : " + score; }
public void AmmoCount(int count)
{ ammoText.text = "Ammo: " + count; }
In Unity, we copy and paste the score field to create our new ammo count field, and of course, remember to drag it into the Inspector for our UI Manager script.
In the Player script, we add a counter for our ammo, and check/set it as needed.
[SerializeField] private int playerLives = 3;
[SerializeField] private int playerScore = 0;
private void Start()
{
NullCheckOnStartup();
uiManager.AmmoCount(ammoCount);
transform.position = new Vector3(0, 0, 0);
}
private void FireLaser()
{
if (playerLives < 1) { return; }
if (ammoCount < 1) { return; }
ammoCount--;
uiManager.AmmoCount(ammoCount);
Instantiate((tripleShot) ? tripleShotPrefab : laserPrefab
, transform.position + laserOffest
, Quaternion.identity);
laserCanFire = false;
StartCoroutine(LaserCoolDown());
}
We could of course leave it at this, but our code so far does beg a few questions. First the obvious, yes, there is an Ammo PowerUp coming so the Player can reload. So, we’ll let that one sit for a moment. The second might be, should the Triple Shot reduce the ammo count by one or three? As written now, only one. But it seems more sensible to me that it cost three. Not only does that correspond visually, it adds a new twist to our strategy. The player might actually decide not to take the Triple Shot power up. Finally, our fire control is getting increasingly complex, and the Player script is taking on an increasing amount of responsibility. i.e. it’s two to four times longer than any of the other scripts. So maybe it’s time to break it up a bit.
Rather than add new features, let’s first pull out the code into a new game object. For the most part, this is just literally pulling the code out of the Player.cs script and pasting it into our new script.
Create an empty game object as a child to the Player game object. Rename it to Weapons and create a script for it. FireLaser()
, LaserCoolDown()
, PowerUpTripleShot()
will copy over mostly intact. Along with that laserPrefab
, tripleShotPrefab
,uiManager, tripleShotTimer, fireRate
and laserOffset
will also come over.
ammoCount
however will be converted to a property. The reason for this will be obvious in a moment. We will also turn the original tripleShot
boolean into a public property name TripleShotEnabled()
. In the setter method we will also start the PowerUpTripleShot()
coroutine. To the FireLaser()
method we will add some additional logic concerning the Triple Shot.
public void FireLaser()
{
if (!laserCanFire) { return; }
if (AmmoCount < 1) { return; }
if (TripleShotEnabled)
{ AmmoCount = AmmoCount - 3; }
else { AmmoCount = AmmoCount - 1; }
uiManager.AmmoCount(AmmoCount);
Instantiate((TripleShotEnabled) ? tripleShotPrefab : laserPrefab
, transform.position + laserOffest
, Quaternion.identity);
laserCanFire = false;
StartCoroutine(LaserCoolDown());
}
This is where we reduce our ammo count. Three if Triple Shot is enabled, and one if it is not. We also check if the ammo count is less than one, so we effectively disable firing. But this is also why we need to make ammo count a property. With the option of removing either one or three at a time from the ammo count, we need to make sure it never goes below zero. None of our logic should be affected, but it’s just weird.
[SerializeField] private int ammoCount = 26;
private int AmmoCount
{
get { return ammoCount; }
set {
ammoCount = value;
ammoCount = (ammoCount < 0) ? 0 : ammoCount;
}
}
In the Player script we mostly just delete all the code we moved to into the Weapons script. With most of the logic moved there is not even much that needs changed from working with local variables and methods to invoking the methods in our new Weapons component. The last few changes are not really needed, but with the movement of other code, we can move some of our other logic around a bit.
private bool PlayerHasFired
{ get {
if ( Input.GetKeyDown(KeyCode.Space)
&& playerLives > 0) { return true; }
return false;
}
}
private void Update()
{
MovePlayer();
CheckBoundaries();
if (PlayerHasFired) { myWeapons.FireLaser(); }
}
private void OnTriggerEnter2D(Collider2D other)
{
switch (other.tag)
{
case "Enemy":
TakeDamage();
break;
case "Enemy Laser":
if (other.transform.parent.GetComponent<EnemyFire>().HasHit) { return; }
other.transform.parent.GetComponent<EnemyFire>().HasHit = true;
TakeDamage();
break;
case "TripleShotPU":
if (!myWeapons.TripleShotEnabled) { myWeapons.TripleShotEnabled = true; }
break;
case "SpeedPU":
if (speedUp == 0) { StartCoroutine(PowerUpSpeed()); }
break;
case "ShieldPU":
if (!shieldAnim.IsActive) { shieldAnim.IsActive = true; }
break;
default:
break;
}
}
My intent was to just move code and add the new feature concerning Triple Shot later. But as you can see, we slipped it in anyway. The Ammo PowerUp will come shortly which brings us to the end of this step.
To be honest, I wasn’t sure I would like moving the weapons related code out, but it’s been bugging from the moment I wrote it. Now that I see it done, I’m pretty pleased with it. In fact, I’m thinking the engine code might be next. After all, we already have a game object for it declared. We’d just need to make the script and move the code.