Search Unity

Communicating between component scripts in Component Based Design

Discussion in 'General Discussion' started by Allan-Law, Sep 15, 2021.

  1. Allan-Law

    Allan-Law

    Joined:
    Jun 28, 2021
    Posts:
    3
    Hello, I'm two months into gameDev & Unity and have been picking up a ton of new concepts / patterns from my learning.
    In my understanding Component based design pattern is better suited for Unity than compared to Inheritance, however, I'm still warping my head around the actual implenmentation in my prototype.

    I wrote some scripts for my third person RPG game: Interactable, Pickupable, Healing.
    These scripts are intended to be thrown into one single gameObject so that when player picks it up he could gain healing buff. I am trying to use events to communicate between the scripts but not sure it is a good way.

    To have healing effect after picking up the object, the healing script would need to have reference to the Pickupable script & subscribe to its OnPickUp event. I could have done this with below, but what if I want to use the healing effect as a skill in another gameObject?
    Code (CSharp):
    1. GetComponent<Pickupable>().OnPickUp += DoHealing();
    How can I decouple the healing script and be able to use it in different combinations?
     
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,573
    Why would you need to subscribe? And why would you want to decouple?

    Your PickableItem can describe its effects, and then Player or Combatant would apply those effects based on description. This can be done through ScriptableObjects or on item itself.

    For example, you could have an item with following description: "Effect: RefillGauge", "Affects: Hitpoints", "Target: Self". All of those would be enums.

    At no point you are going to need OnPickUp.
     
    Allan-Law likes this.
  3. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,934
    Inheritance is generally fine in games/Unity so long as you don't have an animal kingdom's worth of inheritance chains. Most will tell you shallow and wide works best, shallow preferably being only one inheritance step deep.

    I agree though, component works better in many more situations. It does depend on what you're doing, of course.

    You also may want to look at Interfaces, as GetComponent works with interfaces as well. With this you can treat Interfaces as 'components' inside the one script, rather than having lots of little scripts, though that's up to preference.

    At a basic level, my suggestion would be to make a simple Pickup base class, and have different pickups inherit from this class. Down the line when you need more flexible and modular functionality, you can then look at further methods to achieve this (Interfaces, Scriptable Objects, etc).
     
    NotaNaN and Allan-Law like this.
  4. Allan-Law

    Allan-Law

    Joined:
    Jun 28, 2021
    Posts:
    3
    Your comment made me understand that there is no one single best approach to code things, I should probably use the most suitable pattern for each case. As you mentioned, it would make more sense and make things much easier if I use shallow inheritance in many cases instead of compulsing myself to have strictly component design :)
     
  5. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,573
    Yes, shallow inheritance and occasional interface implementation is okay. Unity's GetComponent works with interfaces, by the way.

    The reason why it is recommended to avoid inheritance is because overusing inheritance is a common programmer's trap - people waste their time developing beautiful hierarchies.
     
    Joe-Censored likes this.
  6. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    I wouldn't completely decouple it. I would consider making Healing a subtype of something like Effect. and have my other objects use Effects rather than using Healing directly. Which brings me to...

    Invert that. When an Pickupable is picked up, or an Interactable is interacted with, they can look up any Effect components attached and call ApplyEffect(actor). This also answers your question about how you can use your healing effect elsewhere.

    You don't need the reference in advance, either. It's perfectly ok to do a GetComponent<> from your PickUp() or Interact() methods.

    When deciding what approach to use for something, consider the problem you're solving. Events are useful when you know what will happen but you need to know when it happens. In this case the situation is the opposite. You know when something happens (when the player picks up / interacts with a thing) and you need to know what the effect should be. From what I can tell here, using events for this will make things more complicated without achieving anything further or better.



    P.S: The Scripting section is the place for questions like this one. :)
     
    Last edited: Sep 15, 2021
    NotaNaN likes this.
  7. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    4,044
    For component driven design, I would probably use something like the following to solve this problem.

    I'd have a Pickupable script on the "health pack" game object.

    I'd add a HealOnPickup script to the game object. HealOnPickup would implement IPickupTrigger.

    Pickupable would on pickup GetComponentsInChildren<IPickupTrigger> and call HandlePickup() on each.

    Code (csharp):
    1.  
    2. public interface IPickupTrigger{
    3.   void HandlePickup(Character actor);
    4. }
    5.  
    6. public class HealingTrigger : MonoBehaviour, IPickupTrigger{
    7.   public int HealAmount;
    8.   public void OnPickup(Character actor){ actor.Health += HealAmount }
    9. }
    10.  
    11. public class Pickupable : MonoBehaviour{
    12.   ...
    13.   public void OnPickup(){
    14.     ...
    15.     foreach( var c in GetComponentsInChildren<IPickupTrigger>() ) c.OnPickup( actor );
    16.   }
    17. }
    18.  
    That code is just example, but it should give you the idea. Often instead of using events and the like, what we want to do is slap another monobehaviour on the object and iterate through the attached monobehaviours in order to trigger effects.

    This is also kind of the preferred approach to inheritance, instead of inheritance we're attaching more monobehaviours to an object to mix in additional functionality.

    NOTE: This is not 'correct' or the 'only way' - this is just my preferred approach that embraces the unity design philosophy. Long ago, Unity internally used the "SendMessage" approach, this is basically a strongly typed interface based approach to accomplish the same result.

    I just read the responses, this is basically the same approach @angrypenguin advocates in the previous post.
     
    Last edited: Sep 15, 2021
  8. Stardog

    Stardog

    Joined:
    Jun 28, 2010
    Posts:
    1,913
    You could go through an "entity" component such as "Potion" which references the components and listens to their events and calls their functions, so they don't have to know about each other. Not that this is better than the above approaches, just another way.