Strategy pattern

Tulenber 8 May, 2020 ⸱ Beginner ⸱ 6 min ⸱ 2019.3.13f1 ⸱

The best plan lasts until the first arrow leaves the bow. Mat Cauthon, The Fires of Heaven, Robert Jordan.

We continue our cycle devoted to design patterns and talk about a handy one called Strategy.

Theory

This pattern allows you to change the behavior of the object in runtime, and it is also well suited for highlighting similar algorithms into separate objects and their subsequent reuse.

The theoretical part of the templates described very often, so you can refer to this wonderful article.

Practice

As a practical example, we can look at the behavior of units artificial intelligence in strategic games.

Behavior consists of a set of possible actions, the relationship of which often described with states, another pattern, which we describe in other our article "State Template.“ Each such action is a sequence of steps:

  1. Check the possibility of acting - if it is a shot from a pistol, then the availability of ammo and the distance to the target is checked. For healing, checks include the availability of the first-aid kit and the current level of hit points
  2. Enter animation - unit raises hand with a gun for a shot or takes out a first-aid kit for healing
  3. Action - a shot (creating a flying bullet or removing hit points from the enemy) in the gun case and charging hit points in the case of healing
  4. Exit animation - the unit lowers its hand with a weapon or hides the first-aid kit

As a result, these examples with shot and healing can be converted into several separate strategies:

  1. Action - is a high-level strategy that describes the sequence check-animation-action-animation
  2. Check for the presence of the target - it should be at a suitable distance to the shot, etc.
  3. Hitpoint verification - do not treat yourself without need
  4. Shot - if there are several shots in the game, they will not differ much among themselves
  5. Healing - also pretty repeatable mechanics

This is not a complete set of strategies to describe the examples, but the principles should be clear. If we approach in a similar way to the description of all the possibilities of unit behavior, then we get a limited set of strategies that, with the usage of parameters become very flexible. As a result, this will significantly increase the reuse of code, and therefore reduce the number of bugs and labor costs.

Implementation

Strategies implemented through a description of their interface. Implementation of different algorithms with that interface. And control object that chooses an appropriate strategy and calls it through the described interface.

For example, we implement a small mechanic with a mage launching two different fire spells at a rogue. We will not give a full implementation since, for the most part, it consists of adjusting the animation and selecting assets. Also, an attempt to repeat this small example from scratch on your own will be good practice for working with Unity and resources.

The spell selection uses the Immediate Mode GUI (IMGUI), more details about which you can find in our article about the cheats panels. Work with animations is done through Mecanim, the basic information about which you can obtain from another post "State Template.“

  1. An abstract class used as an interface:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using UnityEngine;

public abstract class Spell
{
    // Trigger to start the animation
    protected int TriggerName;
    // Spell Prefab
    protected GameObject Prefab;
    // Prefab spawn location depends on the type of spell
    protected Vector3 ProjectileOffset;

    public void Fire(Vector3 magePosition)
    {
        // The object of the spell is created with an appropriate shift to the point of appearance
        Object.Instantiate(Prefab, magePosition + ProjectileOffset, Quaternion.identity);
    }

    public int GetAnimationTriggerName()
    {
        // Animation information for this type of spell
        return TriggerName;
    }
}
  1. Strategies in our case will be responsible for the type and offset of the generated spell object, as well as the animation trigger:
  1. Basic spell
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
using UnityEngine;

public class BasicSpell : Spell
{
    public BasicSpell(GameObject prefab)
    {
        Prefab = prefab;
        // Shift relative to the position of the magician
        ProjectileOffset = new Vector3(0.6f, 0, 0);
        // Trigger for the animation of the basic spell
        TriggerName = Animator.StringToHash("Attack");
    }
}
  1. Advanced spell
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
using UnityEngine;

public class AdvancedSpell : Spell
{
    public AdvancedSpell(GameObject prefab)
    {
        Prefab = prefab;
        // Shift relative to the position of the magician
        ProjectileOffset = new Vector3(0.8f, 0.2f, 0);
        // Advanced spell animation trigger
        TriggerName = Animator.StringToHash("Attack_Extra");
    }
}
  1. The mage will be the control object:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
using UnityEngine;

public class Mage : MonoBehaviour
{
    // Parent object for all enemies
    public Transform enemyList = null;
    // Prefab of the basic spell
    public GameObject basicSpellPrefab = null;
    // Prefab of advanced spell
    public GameObject advancedSpellPrefab = null;
    // Mage animation
    private Animator _animator;
    // Flag to control spell cast
    public static bool ProjectileFlag = false;

    // Object for the current spell
    private Spell _spell = null;
    // Basic spell
    private BasicSpell _basicSpell = null;
    // Advanced spell
    private AdvancedSpell _advancedSpell = null;

    // Spell selection settings
    private int _spellSelection = 0;
    private readonly string[] _spellNames = {"Basic", "Advanced"};

    void Start()
    {
        _animator = GetComponent<Animator>();
        
        _basicSpell = new BasicSpell(basicSpellPrefab);
        _advancedSpell = new AdvancedSpell(advancedSpellPrefab);

        // Initially select the basic spell
        _spell = _basicSpell;
    }
    
    void Update()
    {
        // If there are no opponents or there is already a casted spell
        if (enemyList.childCount == 0 || ProjectileFlag)
        {
            return;
        }

        // Start the mage animation to cast the chosen spell
        _animator.SetTrigger(_spell.GetAnimationTriggerName());

        ProjectileFlag = true;
    }

    private void OnGUI()
    {
        // Hide the choice of the spell during the cast and flight of the current spell
        if (ProjectileFlag)
        {
            return;
        }

        // Draw the spell selection panel
        GUI.Box(new Rect (10, 10, 100, 100), "Spell panel");
        _spellSelection = GUI.SelectionGrid (new Rect (20, 40, 80, 60), _spellSelection, _spellNames, 1);

        // If there are no changes, we do nothing
        if (!GUI.changed)
        {
            return;
        }

        // When choosing a basic spell, make it active
        if (_spellSelection == 0)
        {
            _spell = _basicSpell;
            return;
        }

        // When choosing an advanced spell, make it active
        _spell = _advancedSpell;
    }

    // Create the current spell at the right time from the cast animation
    public void Fire()
    {
        _spell.Fire(transform.position);
    }
}

Result

Depending on the chosen spell, the mage starts different spell animations and shot different projectiles.
Result

Conclusion

The implementation of the strategy template itself is not something complicated; in fact, it is just usage of the object inheritance. The biggest challenge will be to isolate the game mechanic into a structure that would be effective for a particular project. Most likely, before this happens, it will undergo several iterations of refactoring. Still, the sooner you begin responsibly approach to the creation of this structure, the easier it will be for you to support the project on a decent level. See you next time! =)



Privacy policyCookie policyTerms of service
Tulenber 2020