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:
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
Enter animation - unit raises hand with a gun for a shot or takes out a first-aid kit for healing
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
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:
Action - is a high-level strategy that describes the sequence check-animation-action-animation
Check for the presence of the target - it should be at a suitable distance to the shot, etc.
Hitpoint verification - do not treat yourself without need
Shot - if there are several shots in the game, they will not differ much among themselves
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.“
usingUnityEngine;publicabstractclassSpell{// Trigger to start the animation
protectedintTriggerName;// Spell Prefab
protectedGameObjectPrefab;// Prefab spawn location depends on the type of spell
protectedVector3ProjectileOffset;publicvoidFire(Vector3magePosition){// The object of the spell is created with an appropriate shift to the point of appearance
Object.Instantiate(Prefab,magePosition+ProjectileOffset,Quaternion.identity);}publicintGetAnimationTriggerName(){// Animation information for this type of spell
returnTriggerName;}}
Strategies in our case will be responsible for the type and offset of the generated spell object, as well as the animation trigger:
Basic spell
1
2
3
4
5
6
7
8
9
10
11
12
13
usingUnityEngine;publicclassBasicSpell:Spell{publicBasicSpell(GameObjectprefab){Prefab=prefab;// Shift relative to the position of the magician
ProjectileOffset=newVector3(0.6f,0,0);// Trigger for the animation of the basic spell
TriggerName=Animator.StringToHash("Attack");}}
Advanced spell
1
2
3
4
5
6
7
8
9
10
11
12
13
usingUnityEngine;publicclassAdvancedSpell:Spell{publicAdvancedSpell(GameObjectprefab){Prefab=prefab;// Shift relative to the position of the magician
ProjectileOffset=newVector3(0.8f,0.2f,0);// Advanced spell animation trigger
TriggerName=Animator.StringToHash("Attack_Extra");}}
usingUnityEngine;publicclassMage:MonoBehaviour{// Parent object for all enemies
publicTransformenemyList=null;// Prefab of the basic spell
publicGameObjectbasicSpellPrefab=null;// Prefab of advanced spell
publicGameObjectadvancedSpellPrefab=null;// Mage animation
privateAnimator_animator;// Flag to control spell cast
publicstaticboolProjectileFlag=false;// Object for the current spell
privateSpell_spell=null;// Basic spell
privateBasicSpell_basicSpell=null;// Advanced spell
privateAdvancedSpell_advancedSpell=null;// Spell selection settings
privateint_spellSelection=0;privatereadonlystring[]_spellNames={"Basic","Advanced"};voidStart(){_animator=GetComponent<Animator>();_basicSpell=newBasicSpell(basicSpellPrefab);_advancedSpell=newAdvancedSpell(advancedSpellPrefab);// Initially select the basic spell
_spell=_basicSpell;}voidUpdate(){// 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;}privatevoidOnGUI(){// 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(newRect(10,10,100,100),"Spell panel");_spellSelection=GUI.SelectionGrid(newRect(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
publicvoidFire(){_spell.Fire(transform.position);}}
Result
Depending on the chosen spell, the mage starts different spell animations and shot different projectiles.
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! =)