DRY / Reusable Components - Scripts or Interfaces or Inheritance?

Scenario: I have many types of game objects that should react to a Bomb explosion in the same way, for instance, call a method called ReactToExplosion() which decrements some health and applies some force. This should happen when any Bombable objects collide with the Bomb radius CircleCollider2D.

I tried a couple things and got stuck:

  1. Create a Script called IsBombable and attach it to any objects that are bombable. In this script, I added code to the OnTriggerEnter2D method but it wouldn’t work because I have already defined that method in the object’s main script Unity only calls the method once.

  2. Create an interface IBombable and implement it on any objects that can be bombed. I still have to code the interactions, though, because each object must implement any interface methods.

Question: What’s the best way to do this without recoding the OnTriggerEnter2d() logic over and over? Ideally, I’d like to create a common method that takes a Vector force and origin point and calls this whenever any object that can be bombed collides with a bomb’s explosion collider.

Thank you. :smile:

How about base class?

public abstract class Bombable : MonoBehaviour 
{
    public void Explode() 
    {
        // Some explosion logic
    }
}

public class Door : Bombable { }

public class Car : Bombable { }

Don’t use subclasses and inheritance. What if you have an object that can be a bomb sometimes and a power source other times? Which do you inherit from? You won’t be able to create a base class that encompasses all possibilities, since you never know what new functionality a child class will need. And even if you do, every child class inherits all the baggage from the base class, even if it doesn’t need it all. It just creates a huge, unmanageable mess. Inheritance has its uses, but it’s not appropriate in and of itself for composable object architectures like the one you’re describing.

Perhaps have a trigger script that implements generic logic for OnTriggerTrigger2D(). One of the reasons for putting this in its own class is that there’s currently a bug in which OnTriggerEnter2D() currently gets called every fixed update rather than just once when the trigger is entered. You may need to add extra logic to handle this.

The trigger script can have a C# event that handlers can register with. When it gets triggered, it can invoke the event to notify all of the handlers. For example (warning: untested code just typed in here, since I’m not near Unity):

public class Trigger2D : MonoBehaviour {

    public event Action TriggerEntered = delegate {};

    public void OnTriggerEnter2D(Collider2D other) {
        TriggerEntered();
    }
}

For brevity I omitted ‘using’, etc. You could use System.EventHandler or a custom delegate instead of System.Action if you want to pass data to the handler.

And then add bomb script(s) that register with the trigger:

public class BombType1 : MonoBehaviour {

    private Trigger2D trigger2D;

    void Awake() {
        trigger2D = GetComponent<Trigger2D>();
    }

    void OnEnable() {
        if (trigger2D != null) trigger2D.TriggerEntered += Kaboom;
    }

    void OnDisable() {
        if (trigger2D != null) trigger2D.TriggerEntered -= Kaboom;
    }

    public void Kaboom() {
        // Your bomb code here.
    }
}

You could even use inheritance appropriately in this case, such as:

public abstract class AbstractBomb : MonoBehaviour {

    private Trigger2D trigger2D;

    void Awake() {
        trigger2D = GetComponent<Trigger2D>();
    }

    void OnEnable() {
        if (trigger2D != null) trigger2D.TriggerEntered += Kaboom;
    }

    void OnDisable() {
        if (trigger2D != null) trigger2D.TriggerEntered -= Kaboom;
    }

    public abstract void Kaboom();
    }
}

public class BombType1 : AbstractBomb {
    public override void Kaboom() {
        // Your bomb 1 code here.
    }
}

public class BombType2 : AbstractBomb {
    public override void Kaboom() {
        // Your bomb 2 code here.
    }
}

Thank you both for the replies!

I’d like to avoid traditional inheritance because so many types of objects could be affected by an explosion (environment, players, enemies, etc ). I’ll implement the generic OnTriggerEnter2D() delegate and see how it works. I think this is the best way to share general functionality.

Tony, do you mind explaining how to pass data to the TriggerEntered() method?

Instead of using System.Action, you can use System.EventHandler or define your own custom delegate:

// This defines the syntax of the delegate method:
public delegate void TriggerDelegate(Collider2D other);

// Our trigger component:
public class Trigger2D : MonoBehaviour {

    public event TriggerDelegate TriggerEntered = delegate {};

    public void OnTriggerEnter2D(Collider2D other) {
        TriggerEntered(other);
    }
}    

public abstract class AbstractBomb : MonoBehaviour {

    private Trigger2D trigger2D;

    void Awake() {
        trigger2D = GetComponent<Trigger2D>();
    }

    void OnEnable() {
        if (trigger2D != null) trigger2D.TriggerEntered += Kaboom;
    }

    void OnDisable() {
        if (trigger2D != null) trigger2D.TriggerEntered -= Kaboom;
    }

    public abstract void Kaboom(Collider2D other);
    }
}

public class BombType1 : AbstractBomb {
    public override void Kaboom(Collider2D other) {
        // Your bomb 1 code here.
    }
}

public class BombType2 : AbstractBomb {
    public override void Kaboom(Collider2D other) {
        // Your bomb 2 code here.
    }
}

Ah, great! So any method added to the delegate must have that signature?

Yup, you got it. (Although please keep in mind that my code above may have syntax errors or typos since I’m not near Unity right now.)

That’s what interface are there for.

I agree with you in principle. Interfaces would be better than inheritance. But events and delegates are the best fit for the observer pattern described above.

I’m not sure about that. OnTriggerEnter2D would fire whenever one trigger meet another collider. How do you know the exploding object only have one trigger and the right one was collided with? How do you differentiate if something should trigger it while other should not? Switch case in the receiver? Yark!

I’m not sure this would fall properly into an observer pattern since nobody observe anything. It’s more like events re-routing to me.

Finally, this is a bit too anonymous for my taste. What if the exploding object need to send damage to those receiving it? How about different damage type? (Ex.: Acid explosion) How about line of sight test, who does it? Or lowering damage with distance? Force push? Or object being destroyed by other means than explosion? Sword slash?

Okay, that’s fine. My rationale for the observer pattern (or event system if you want to call it that) is that the bomb just wants to be notified when something enters its trigger area. But to each their own. There’s more than one way to skin a cat. :slight_smile:

Can classes implement multiple Interfaces in C#? If I created many kinds of reactions (Bombable, Breakable, Healable, etc) could I implement those interfaces in my class?

Yes. You can only have 1 based class, but unlimited number of interfaces.