loading...

Implementing the Strategy design pattern using Scriptable Objects in Unity

eriksk profile image Erik Skoglund ・4 min read

Implementing the Strategy design pattern using ScriptableObject in Unity

Strategy is a design pattern from the book Design Patterns (GOF)
The purpose of the pattern is to enable changing behaviour at runtime in a clean way (not a bunch of if-statements).

You can read more about the Strategy pattern here as it will not be covered here.

Implementing the pattern in C# is pretty straight forward and can be explained as simple as this:

C# example

Imagine you have a Player class that can perform different kinds of attacks. This could look something like this.


    class Player
    {
        void Attack(AttackType type)
        {
            if(type == AttackType.Melee)
            {
                // TODO: melee logic
            }
            else if(type == AttackType.Crossbow)
            {
                // TODO: crossbow logic
            }
        }
    }

Adding a new attack means adding a new if-statement. Not to mention this method will quickly get very long and hard to follow. Commonly, attacks will need different parameters. Both might need some kind of damage, but crossbow will need range and even possibly ammo, while melee won't. Of course we could pass all of those parameters to the Attack method. But strategy can make this a lot cleaner.

Instead, using Strategy we can make this a bit cleaner:


// Player.cs
class Player
{
    void Attack(IAttack attack)
    {
        attack.Execute(this);
    }
}

// IAttack.cs
interface IAttack
{
    void Execute(Player player);
}

// MeleeAttack.cs
class MeleeAttack : IAttack
{
    public int Damage { get; set; }

    void Execute(Player player)
    {
        // TODO: Melee attack logic
    }
}

// CrossbowAttack.cs
class CrossbowAttack : IAttack
{
    public int Damage { get; set; }
    public float Range { get; set; }
    public int Arrows { get; set; }

    void Execute(Player player)
    {
        // TODO: Crossbow attack logic
    }
}

Alright, there's a little bit more code. We moved them into different files and separated each attacks' functionality into different classes all implementing IAttack. Now the player only needs to know about IAttack and passes itself as a parameter to the attack it is using.

Adding a new attack now is as simple as adding a new attack-class. No changes in the Player-class are needed.

Unity example using ScriptableObject

So this is all nice and dandy. But what about doing this in Unity? I think there are benefits from doing this with just C# in Unity, but when it really shines is when using Unitys ScriptableObject for the implementations of each strategy.

Read more about ScriptableObject

The benefit of ScriptableObject is that they can be created as assets. This means you can drag-drop your strategies in the Unity Editor directly to game objects. (This also comes with a cost, we'll get to that later)


// Player.cs
class Player : MonoBehaviour
{
    public void Attack(IAttack attack)
    {
        attack.Execute(this);
    }
}

// IAttack.cs
interface IAttack
{
    void Execute(Player player);
}

// Attack.cs
public abstract class Attack : ScriptableObject, IAttack
{
    public abstract void Execute(Player player);
}

// MeleeAttack.cs
[CreateAssetMenu(menuName="Custom/Attacks/Melee Attack")]
class MeleeAttack : Attack
{
    public int Damage = 1;

    public override void Execute(Player player)
    {
        // TODO: Melee attack logic
    }
}

// CrossbowAttack.cs
[CreateAssetMenu(menuName="Custom/Attacks/Crossbow Attack")]
class CrossbowAttack : Attack
{
    public int Damage = 3;
    public float Range = 20f;
    public int Arrows = 5;

    public override void Execute(Player player)
    {
        // TODO: Crossbow attack logic
    }
}

A couple of things are different here:

  • We added an abstract class Attack that inherits ScriptableObject and implements IAttack
  • MeleeAttack and CrossbowAttack inherit Attack instead of using the interface directly
  • An attribute CreateAssetMenu was added - this makes it possible to right-click in the Unity Editor and add a new asset for that specific type of attack.
  • The properties were changed to fields - meaning we can now also edit these values in the Unity Editor for our assets

This also means it is possible to create different assets for the same type of attack. For example we could have a weak melee attack and a strong one, and perhaps add a cost in stamina for using that attack.

The flexibility of this is pretty great. And the separation of the logic from the Player makes it even easier.

Another thing means that someone who isn't a programmer can easily add new configurations of attacks without touching the code!

The drawbacks

Honestly this isn't a big deal but might save you some time: Remember that these ScriptableObjects are Assets. This means when you attach a ScriptableObject to a game object, it will use the same instance of that asset. Even if two different objects are using it.

That is why State should never be stored in the ScriptableObject. Think of it as configuration and behaviour only.

Another thing is that you can't just instantiate a ScriptableObject on the fly, Unity doesn't allow it. However you can use this factory method:


ScriptableObject.CreateInstance()

This will give you a new instance of a ScriptableObject that is not an asset.

This is my first post here so, hello! :)

Discussion

pic
Editor guide
Collapse
patrykszylin profile image
Patryk Szylin

Player.cs
public void Attack(IAttack attack)
{
attack.Execute(this);
}

How do you pass an attack to this function? Do you use a custom serializer to expose IAttack in the inspector??