[6] - Tower Defense & Scriptable Objects

Upcoming Game

Over the past few weeks I have been working on a group project to make a 2D Tower Defense game. The idea was to have a project that would have a smaller scope that was easily executable prior to moving onto a larger project. Like many others, a sizable part of my childhood PC gaming was on browser games such as ‘Thing Thing’, ‘Adrenaline Challenge’, ‘Tanks’ but most relevant to the topic of this post is ‘Bloons Tower Defense’: a tower defense game where you prevent balloons reaching the end of the path. The player builds towers of a mixed variety of wackiness themes to better fend from increasing levels of balloon types.

Tower defense games have two key aspects: enemies and towers to eliminate the enemies. The difficulty of the enemies tends to increase as the game progresses, and towers are also unlocked as the game progresses for the player to better eliminate the enemy. The simplicity of the game made it a viable project that would be easily executed from design to release.

Scriptable Objects

Image 1: Machine Gun Tower Scriptable Object Inspector View in Unity Editor

Image 1: Machine Gun Tower Scriptable Object Inspector View in Unity Editor

To store the data of objects in our game we used Scriptable Objects: an object that stores data that can be used by other objects.

Scriptable Objects can be used in a wide range of uses such as items in an RPG, weapons in a shooter, user interface settings and visuals, and in our case, towers and enemies.

The benefit of Scriptable objects is that it is referenced, not instantiated by objects that use them. This is very useful for when you want to use the same data without needing multiple copies of it. Multiple copies means higher memory usage - fine if the data was unique, which in this case where there may be 50 of one enemy type, it isn’t. All of those enemies can use the same Scriptable object, instead of having their own.

I came across Scriptable objects or ‘Scriptables’ in 2018 from a tutorial by Brackeys, and where appropriate, I have used them ever since. A Tower defense game is the perfect scenario to use them as the repeated use of the same object will benefit greatly by being performant and making new types of objects much easier. For our game we created three main Scriptable Objects: the towers, the projectiles and the enemies.

An example of a Scriptable Object can be seen in Image 1. The tower Scriptable contains variables such as the name, the sprites to visualise the tower, the maximum range a tower can acquire a target, the cost to build a tower and the sound it makes on firing a projectile. When a tower is created the tower Scriptable and also a projectile Scriptable are referenced in a Tower Controller script that controls target acquisition and firing. The same Scriptables are used in a UI Manager for displaying available Towers to the player.

We made separate tower and projectile Scriptables as it means we can pass a tower a different projectile to fire

Using Scriptable Objects makes making towers must easier too. A new Scriptable can be created, the data inputted into the Scriptable and that object can be passed into an object that supports it.

Another benefit of Scriptable Objects is that changes made at runtime will also be saved. This means our tweaking for difficulty scaling was made much easier by not needing to restart the game to make changes. For example, an enemy is able to get to the goal too quickly? The Scriptable for that enemy can be changed at runtime to a more appropriate number. This ultimately makes level design a smoother process.

How to create and use a Scriptable Object

I would not be able to do Brackey’s tutorial justice with a comprehensive tutorial on Scriptable Objects, but for a quick note on how to create and use a Scriptable Object I have added some steps below

  1. Creating a Scriptable Object script

  2. Creating an object from the Scriptable Object

  3. Add the created Scriptable to an object to use it

For this example, I will use a slimmed-down version of the Tower Scriptable shown in Image 1, and a script for the Scriptable to be used in. Below is a ScriptableObject with a cooldown, damage and range variable. The script also contains a Sprite that we can use to provide an image for what will be displayed, and a colour.

[CreateAssetMenu(fileName = "Tower", menuName = "Scriptables/Tower")]
public class TowerScriptable : ScriptableObject
{
    public float cooldown = 1f;

    public int damage = 5;

    public float range = 10f;

    public Sprite sprite;

    public Color colour = Color.red;
}
Image 2: Three Tower Scriptables shown in the Inspector

Image 2: Three Tower Scriptables shown in the Inspector

Once this base is created we can create as many towers as we like! For the benefit of variety I have created three towers, each with their own unique stats. I have shown how this looks in Image 2 with the three Tower objects each in their own Inspector tab.

Now that we have a Tower Scriptable, it needs something to use it. Below is a rough Tower Script that takes in a TowerScriptable, and uses its data to do damage on a valid target, and cooldown. By using a Tower Scriptable, we can turn a barebones Tower object and give it all the data it requires to be any sort of tower.


public class TowerScript : MonoBehaviour
{
    public TowerScriptable tower;

    private bool isTowerOnCooldown = false;

    public EnemyController target;
    
    private void Update()
    {
        if (!isTowerOnCooldown && target != null)
        {
            //Fires at the target, damaging it
            Fire();
        }
    }

    public void Fire()
    {
        //Inflicts damage on the target
        DoDamage();
        //Set the tower to be on cooldown
        isTowerOnCooldown = true;
        //Start the cooldown reduction
        StartCoroutine(RefreshCooldown());
    }

    private void DoDamage()
    {
        target.TakeDamage(tower.damage);
    }

    private IEnumerator RefreshCooldown()
    {
        //Waits until the alloted time has elapsed
        yield return new WaitForSecondsRealtime(tower.cooldown);
        //Sets the tower to be able to fire again
        isTowerOnCooldown = false;
    }
}

To add a Scriptable to a script/object, this can be done either by adding it to a Prefab and instantiating/placing it in a scene, or otherwise having a spawner object that holds all the Tower Scriptables in a list and adds the Scriptable to an object. For our Tower Defense game we spawn in a Tower Prefab, assign the Scriptables and from there set its sprites and scale. The functionality references the data in the Scriptables and the Tower does its job.

Image 3: Level Scriptable

Image 3: Level Scriptable

Future

As far as Scriptable Objects go for our tower defense game, there was one more I added - a Level Scriptable Object. This Scriptable contains a list of enemy Scriptables, and a list of a class that contains both a tower and projectile Scriptable. This was done for the ability to control in a Scriptable, rather than hard-coded, when enemies spawn and how many, and the wave that a tower is unlocked. Just like how a Tower Prefab is given a Tower Scriptable, the level Scriptable is given to the Wave Controller that uses the data to spawn in each wave and for the UI Manager to unlock a tower when said wave is reached. Image 3 above is a glimpse into the Main Level Scriptable we use.

With this setup, this makes it possible to implement levels of different difficulty scaling and intensity.

Release?

As of the publication of this post, the game is near completion and we will be looking to release the game, for free, very soon!

Previous
Previous

[7] - TD Town Out Now!

Next
Next

[5] - Patterns Out Now!