Strategy pattern and UnityEvents. (Q) how to use'em in cooperation

Hi there!

I’m now working on a kind of home-brew weapon system and while the process I’m stuck in finding proper way for implementing strategy pattern with UnityEvents inside. But before we dive deep in certain problem let me draw the background.

Well, the game designing’ terms are the following:

  • each weapon belongs to one of defined types: handheld (clubs, knives etc), ballistic (fire with any type of solid bodied projectiles (arrow, bullets, grenades, rockets), energetic (laser etc)

  • due to its type weapon executes shooting method in different way:

  • ballistic ones spawn bullets - GOs which deal damage by colliding the target

  • energetic ones cast a ray for the same purpose

My architectural intentions are:

  • I’d like to follow SOLID and MVC principles (eg: I separate weapon’ settings into Scriptable Object, weapon FX - into stand-alone component and so on)
  • I use weapon controller on the weapon’ GO (it has references to data and keeps shooting strategy and events)
  • and I use weapon operator on the player’ GO which equips weapon and performs shooting by input
  • I’m intended to use as much Unity build-in components as possible (so why we use engine if don’t know what stuff it provides and remake a lot of things manually?), so I choose UnityEvents rather then delegates/actions

Thus, here goes the code and the problem - > defining OnShoot event inside WeaponBluprint but not inside ShootingStrategy

using System;

[CreateAssetMenu(fileName = "NewWeaponData")]
[Serializable]
public class WeaponData : ScriptableObject
{
      [SerializeField]
      float damage; 
    
      [SerializeField]
      float shootingRange;
    
      [SerializeField]
      float ImpactForce;

      #region Getters ...#endregion
}
public interface IShootStrategy
{
       // This method will be implemented in ShootingStrategyBallistic
       void Shoot(Transform muzzle, ProjectileBlueprint projectile);
  
       // This method will be implemented in ShootingStrategyRaycast
       void Shoot(Transform muzzle, float damage, float range, float impactForce);
}
public abstract class ShootingStrategy : IShootingStrategy
{
      //Ballistic
      public virtual void Shoot(Transform muzzle...) {}

      //Raycasting
      public virtual void Shoot(Transform muzzle...) {}
}
public class ShootingStrategyBallistic : ShootingStrategy
{
      public override void Shooting(Transform muzzle, ProjectileBluprint projectile)
      {
            ProjectileBlueprint temp = (MonoBehaviour).Instantiate(...);
      }
}
using UnityEngine;
using UnityEngine.Events;

//TODO: refactor this to WeaponBallistic : WeaponBlueprint
[DisallowMultipleComponent]
public class WeaponBlueprint : MonoBehaviour
{
      WeaponData data;
      Transform muzzle;
      ProjectileBlueprint bullet;
      IShootingStrategy shootingStrategy = new ShootingStrategyBallistic();

      UnityEvent OnShoot = new UnityEvent();

      public void ExecuteShooting()
      {
            shootingStrategyBallistic.Shooting(muzzle, bullet);

            if(OnShoot != null)
               OnShoot.Invoke();
      }
}
using UnityEngine;

[DisallowMultipleComponent]
public class WeaponOperator : MonoBehaviour
{
      WeaponBlueprint initialWeapon;
      WeaponBlueprint equippedWeapon;

      // ...

      void Update()
      {
           if(equippedWeapon)
           {
              if(Input.GetButtonDown("Fire1"))
                 equippedWeapon.ExecuteShooting();
           }
       }
    
      //...
}

So my main concern is the “man-in-the-middle” abstract ShootingStrategy class. I considered it to hold OnShoot event and trigger in virtual Shooting() and then reuse it as base.Shooting() in all overrides.

But non-MonoBehaviours don’t expose in Inspector and I can’t use visual assigning of listeners to OnShoot. I know I can use AddListener() but it forces me to keep references inside Strategy class - I think this approach is merely out of pattern’s concept. As I mentioned above I can use classical delegates inside abstract but… they’re not a Unity stuff and have no Inspector view also (besides requirement of tracking OnEnable/OnDisable subscribing).

Why I’m in doubt of keeping event inside WeaponBlueprint class… I think it violates SP pattern, may be not - I don’t explain my concern in more specific way…
What I see obviously: at the point of view of common logic, WeaponBlueprint “can shoot” independently of operator’s actions, because it has ExecuteShooting(). But it is out of “close to reality” behaviour of any weapon and - in code’ terms - duplicates ShootingStrategy() with tiny detail in addition → firing the event.

Maybe any of you can point me the way to implement SP pattern properly with the goal of using UnityEvents?

P.S. I’m a professional game designer not a programmer. Unity scripting is a hobby generally and the way to widen my professional field of view. So I’m sorry if my question sounds like how to shoot in elephant’ leg while catching the bird under the see on Moon.

LOL! That’s not how I expected this question to end …

I think this is a pretty good attempt for a hobbyist programmer. I will question a few of your decisions though.

Why are you using an event of any type? Events are mainly used to reverse a dependency. What I mean is, say you have an object (weapon) which needs to cause an action (shoot) to occur. One option, is for the object to hold the action as a dependency. In other words, for the gun to operate normally, it needs to know about shoot. The other option is for the action to register with the object, which is now a dependency of the action. In other words, shoot needs to know that the gun can be “shot” and registers itself to be informed of that event.

So you see, one of the two things, either gun or shoot, need to know about each other. Events will let us reverse the usual direction of dependency, but not eliminate it all together. If you don’ like that shoot needs to know about gun, then it makes sense to just remove the event from the equation.

One other option is to have a third party do the event wire up. In this case some kind of “manager” will know about both your gun and your shoot. Then it will register shoot to gun’s onShot event on behalf of shoot. Personally, I don’t think this is a better alternative since a) you need a third object with dependency to both things and b) shoot still needs to know about “onShot” event in that it’s method that gets registered must have a signature that matches the event. This is a weakened dependency but a dependency all the same.

I say, just let your gun have an IShootStrategy component and forget about UnityEvent.

Another question. Why use an abstraction (interface) if your implementations care about different information. What I mean is

This interface makes no sense. Interfaces are supposed to unify two or more implementations so that they are indistinguishable. If you plan to accept any IShootStrategy but then use different method depending on the underlying type, you are violating the Liskov Substitution Principle. Your consumer of IShootStrategy needs to be indifferent to which kind of strategy actually exists behind the interface, so you should design the interface to work for every kind of implementation. Here is an example:

public interface IShootStrategy
{
  // This method will be implemented in ShootingStrategyBallistic, ShootingStrategyRaycast and any other ShootingStrategy we might dream up later.
  void Shoot(Transform position, Quaternion rotation);
}

public class BallisticShootStrategy : IShootStrategy
{
  IBallisticBlueprint ammoType;
  public void Shoot(Transform position, Quaternion rotation)
  {
    UnityEngine.Object.Instatinate(ammoType, direction, rotation);
  }
}

public class RaycastShootStrategy : IShootStrategy
{
  int damage;
  public void Shoot(Transform position, Quaternion rotation)
  {
    RaycastHit hitInfo;
    if (UnityEngine.Physics.Raycast(direction, rotation, out hitInfo)
    {
      var damageable = hitInfo.collider.gameObject.GetComponent<IHealthSystem>(); // made up IHealthSystem .. you need to implement something similar yourself.
      if (damageable != null)
        damageable.Damage(damage, position);
    }
  }
}
1 Like

Thanks for answer my question.

if you don’t mind I will answer in reverse. I’m new with Strategy pattern, so didn’t try before to implement this and explore it widely. My current scripts you see are the first attempt. While writing my IShootingStrategy I also caught myself on thinking that two different methods look weird in terms of the pattern.

I got your example and will try follow that way.

Your first question… well, it will be hard to explain in terms of pure programming. I admire how UnityEvents look like in Inspector, I’m a visual guy and prefer to see something rather than other kind of perception. It also gives me some kind of “map” of how entity-inside components communicate with each other - http://take.ms/DL8hr. But I also faced situations when I had to use AddListener() in code, and that fact doesn’t make a difference for me between using UE or C# delegates.

I also understand that simple references to my WeaponFXController and WeaponAmmoCounter inside Weapon class are more code-transparent maybe but not so easy to follow in mind without some UML preproduction and so on.

But the main reason of such using of UE is… well, in my mind I interpreter these events as some kind of “internal dialogue” between different “personalities” of one entity - the weapon.

Weapon shoots and says to itself - “Well, the shot was made… what’s next… aha, my imaging friend, AmmoCounter, where you?”
AmmoCounter “I’m here honey, what’s wrong?”
Weapon “I made a shot…again… Please, add one to your counter”
AmmoCounter “Dear, I don’t want to scare you but I must. We have only free bullets left.”
Weapon “Oh… you’re so greedy as always”

… and then WeaponFXController comes and makes life brighter :wink: Reactive programming I guess…

Yes, at the beginning of my practices when I followed the tutorials everything was inside one script which execute all stuff solely. Now it is time to split code into parts )

Another, more gameplay oriented example. I work on my “climates” concept. We have two for now (more in perspective) subsystems - so called climates - where player’s actions influence on the situation on the level. As much noise guns produce as much aggressive mobs become and start tracing player (“noise climate”). As more mobs are killed as less dangerous situation around player (“threat climate”).

And there UEs would help me to track such dependences visually but… AddLstener() goes on stage.

First off this is not the Strategy Pattern. the primary traits of adhering to the strategy pattern is that the targeted behaviour can be changed at runtime and by outside sources. meaning that external sources can easily change the behaviour. this is just polymorphism.

Non-monobehaviours can be shown in the inspector. You can make custom classes appear in the inspector if they are marked with the [System.Serializable] attribute and are publicly rooted to another UnityObject (meaning you can only see them if they are a property of a ScriptableObject or monobehaviour and made public).

just make ShootingStrategy a scriptable object asset and have WeaponBlueprint store the shootingStrategy as the abstract ShootingStrategy class instead of IShootingStrategy (the inspector can’t serialize/deserialize interfaces natively so fields stored as interfaces will never show up).

I would also put the projectile blueprint (assuming its a prefab reference) inside the weapon data class and IShootingStrategy would simply have one function IShootingStrategy.Shoot(Transform source, WeaponData data). that way its completely left up to the concrete strategies to figure out how to use all the data, thus WeaponBlueprint doesn’t have to worry if it needs to do the raycast or the projectile-based shooting, and it will make more sense for your hand-held weapons as well.

if you do make ShootingStrategy a scriptableObject and try to put the OnShoot event into the class, do note that while you can use the UnityEvent in the inspector, only listeners referencing to assets (other scriptableObject assets for example) will serialize correctly. if you try something like reference a component and run a function from it, usually nothing will happen. There are workarounds , but suffice to say you will be required to write at-least some code.

Um… https://en.wikipedia.org/wiki/Strategy_pattern#UML_class_and_sequence_diagram
Two options: or we misunderstood each other or misunderstood what SP is

I know about System.Serializable and so one. I meant that UnityEvent which put in non-Mono class will not be shown in Inspector in general way except of SO classes

(may be with custom editor - will be but but it’s not worth the efforts)

Sounds interesting, will try it, thx

It sounds like breaking my initial approach to use of UEs… Any way I’m starting to loose the line where and how I (with my current level of understanding this stuff) should use them. And also, using them or not, operating w/o inspector, only with AddListener() methods, make them indistinguishable from delegates/event Actions

That Wiki page is woefully incomplete. Can you tell me (based on that wiki page) what the difference is between a Strategy Pattern and a State Machine? because the information presented on that wiki page is vague enough that it could represent either pattern (you could remove all the words for strategy and replaced it with state machine and it’d still be true).

As I’ve said before, the two primary issues to why I said its not the strategy pattern is:

  • currently your code can’t change the strategy at runtime
  • the strategy can’t be changed by others (the shooting strategy field wasn’t public, or there wasn’t any public property/function that let them directly change the strategy)

This is what I meant.

[System.Serializable]
public class MyCustomEventContainer
{
    public UnityEvent OnAction = new UnityEvent();
}

public class MyCustomEventBehaviour : Monobehaviour
{
    public MyCustomEventContainer container;
}

MyCustomEventContainer is a Non-Monobehaviour, Non-Scriptable Object class. but its data can be shown in the inspector. at first its not super obvious to everyone as to what is needed to get some fields rendered in the inspector

For data in custom system classes to be shown in the inspector:

  • Be in a Serializable class (MyCustomContainer has [System.Serializable])
  • Be a field with either a “public” accessor or the [SerializeField] attribute.(OnAction is public)
  • NOT be stored as an interface, abstract, generic, or delegate reference.(OnAction is stored as a concrete UnityEvent)
  • the class Must be rooted to a UnityObject as a serializable fieald as in the unity object (eg. Scriptable Object or Monobehaviour) must have a public or [SerializeField] field linked to that)(MyCustomEventBehaviour has a public field reference for MyCustomEventContainer.
  • The field itself must already have a property drawer written up with the UnityEditor namspace (most primitive types and most serializable Unity classes have built-in drawers).

[/QUOTE]

Just think of the objects you assign to a UnityEvent in the spaces where each exsist. There are two main types of spaces: Scene-space, and Asset-space. If the unity event is on an asset like a ScriptableObject, that event is in the Asset-space and via the inspector should only link with other Assets(you can link to scene-space but that needs to be tightly managed via code). if the unity event is on some object that exists in the scene then its in the Scene-space and can link to both the asset-space and other objects within the same scene.

The primary benefit UnityEvents have over Events and delegates is that they can be set up via the inspector, they have a user-interface. its great when you want to link things up in a scene without writing out any code dependencies, but don’t limit yourself to only using that UI. there are limits as to what can be done in the inspector with UnityEvents. For Example there exists UnityEvent<T,U,V,W> classes, but the inspector only supports and serializes UnityEvent and UnityEvent.