Search Unity

Feedback Get Component on Interface property

Discussion in 'Scripting' started by MartinMa_, May 4, 2023.

  1. MartinMa_

    MartinMa_

    Joined:
    Jan 3, 2021
    Posts:
    455
    Hello guys I want ask you for your feedback. I am not very good in C# but want make some nice way how to achieve this:

    I have many IGatherables in my game, all of them have interface IGatherable. I found out that I cannot GetComponent on interface so I make this solution what is working.

    1. I add property do my interface type of GameObject
    Code (CSharp):
    1.  public interface IGatherable
    2.     {
    3.         GameObject _gameObject { get; set; }
    4.     }
    2. In Awake method I set this property to current game object
    Code (CSharp):
    1.     public GameObject _gameObject { get; set; }
    2.     private void Awake()
    3.     {
    4.         _gameObject = gameObject;
    5.     }
    3. I can pass my Interface as param to any method and on referenced gameobject i can call GetComponent.
    Code (CSharp):
    1.   public void Remove(IGatherable gatherable)
    2.         {
    3.             var resourceNode = gatherable._gameObject.GetComponent<Transform>();
    4.         }
    Is it good practice? What do you think about it?
    What I am thinking now, will be better to use Transform instead of GameObject?
    How I understand it if I use gameobject I reference all components what are on it, but when I reference only Transform I reference only one component of gameobject, am I right in this?
     
    Last edited: May 4, 2023
  2. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    if you use the transform data more than you use the game objects active then..hmm its your decision.
    It looks good to me, but we are entering realms of this is infrastructure for a specific game that processes generic objects this way, in one big queue, no matter what the object is, not a dedicated queue. So I guess if you had a design built around that concept then it seems useful. I'd might try something similar but I have no use or design for it in my head right now. Or at least not a design where i would need to iterate huge unsorted list. I am assuming igatherable will have multiple entry types when used. As currently theres no difference between this and a gameobject.

    Practice making stuff its good practice sure. Id be interested to see how a game uses this for some benefit ontop of unity.
     
    MartinMa_ likes this.
  3. MartinMa_

    MartinMa_

    Joined:
    Jan 3, 2021
    Posts:
    455
    Thanks for your response, I am using this list for my worker ai, basically it return closest transform with some resource so then AI go to closest one, sometime it can be tree with wood and sometime rock with stone or even building with some available resources. I wanted to avoid pass as parameter generic transform or gameobject because "what if" you pass gameobject what does not have IGatherable component.
     
    StarBornMoonBeam likes this.
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,531
    A long while back I created an interface called 'IGameObjectSource':
    Code (csharp):
    1.     public interface IGameObjectSource
    2.     {
    3.  
    4.         GameObject gameObject { get; }
    5.         Transform transform { get; }
    6.  
    7.     }
    https://github.com/lordofduct/space...cepuppy.core/Runtime/src/IGameObjectSource.cs

    The nice thing about the shape of it have the names 'gameObject' and 'transform' it mirrors the existing interface of MonoBehaviour and therefore is implicitly implemented by just inheriting from IGameObjectSource:
    Code (csharp):
    1. public class TestBehaviour : MonoBehaviour, IGameObjectSource
    2. {
    3.     //no need to actually implement IGameObjectSource.gameObject or transform.
    4. }
    Then any of my interfaces that are intended to exclusively be components, can inherit from IGameObjectSource, and the behaviour just falls out of it.

    You could even create some extension methods like so and have them behave similar to how you can say 'Component.GetComponent':
    Code (csharp):
    1. public static class GameObjectSourceExtensions
    2. {
    3.     [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    4.     public static T GetComponent<T>(this IGameObjectSource source) => source.gameObject.GetComponent<T>();
    5.  
    6. }
    ...

    Actually... I have yet another interface called 'IComponent' that stands between IGameObjectSource and my component interfaces:
    https://github.com/lordofduct/space...com.spacepuppy.core/Runtime/src/IComponent.cs

    And I have my own SPComponent class that does some added boilerplate including a psuedo-mixin system:
    https://github.com/lordofduct/space...om.spacepuppy.core/Runtime/src/SPComponent.cs

    And all my scripts tend to inherit from this instead, and my interfaces all inherit from IComponent if they're intended to be a component. But the same general idea stands as the simpler version I posted above.
     
    Last edited: May 4, 2023
    CodeRonnie, Ryiah and Bunny83 like this.
  5. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,856
    I would just cast-check if the Interface also happens to be a component.
    Code (CSharp):
    1. if (IGatherable is Component component)
    2. {
    3. }
    That way you aren't tight coupling a gatherable to a game object, so you can - in some places - use it as purely a data representation.
     
    CodeRonnie and Bunny83 like this.
  6. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,990
    I'm not sure what you mean by this statement. Do you mean you want to call GetComponent on the instance that implements your interface? There are several solutions for this.

    First of all an interface does not necessarily be implemented by a MonoBehaviour. So if you have an ordinary class that implements your interface, there is no way to call GetComponent on that object since it would not be a Component and not related to a gameobject. However if you know for sure that the instance you have at hand is implemented by a Component, you can always cast an interface to a Component. If you're not sure if the object is a component, you can use an "is" cast like this:

    Code (CSharp):
    1. public void Remove(IGatherable gatherable)
    2. {
    3.     if (gatherable is MonoBehaviour mb)
    4.     {
    5.         mb.GetComponent<Whatever>();
    6.     }
    7. }
    Though if you know for sure that the instance is a MonoBehaviour derived class, you can directly cast it like that:

    Code (CSharp):
    1. public void Remove(IGatherable gatherable)
    2. {
    3.     MonoBehaviour mb = (MonoBehaviour)gatherable;
    4.     mb.GetComponent<Whatever>();
    5. }
    Of course a normal cast would throw an exception if "gatherable" is not a MonoBehaviour.

    Another option would be to use an interface like this:

    Code (CSharp):
    1. public interface IComponent
    2. {
    3.     T GetComponent<T>();
    4.     T[] GetComponents<T>();
    5.     bool TryGetComponent<T>(out T comp);
    6.     GameObject gameObject { get; }
    7.     Transform transform { get; }
    8.     string name { get; }
    9.     // implement whatever MonoBehaviour methods you may need here
    10.  
    11. }
    This is fully compatible with MonoBehaviour classes. So every MonoBehaviour class could implement this interface. Also keep in mind that you can make interfaces implement other interfaces

    Code (CSharp):
    1. public interface IGatherable : IComponent
    2. {
    3.     // gatherable stuff here
    4. }
    5.  
    Though in this case every IGatherable has to implement all those methods and properties. In case of MonoBehaviour derived classes, that's not an issue as those are all implicitly already available. However with this IComponent interface you could even create non-MonoBehaviour classes (maybe a wrapper or proxy class) as long as it implements those methods.
     
    Nefisto and MartinMa_ like this.
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,990
    I see I was already too slow ^^. Note that with an "is cast" you can simply chain a TryGetComponent which is quite compact:

    Code (CSharp):
    1.     if (gatherable is Component obj && obj.TryGetComponent<SomeOtherComponent>(out var comp))
    2.     {
    3.         // use "comp" here
    4.     }
     
    spiney199 likes this.
  8. MartinMa_

    MartinMa_

    Joined:
    Jan 3, 2021
    Posts:
    455
    I know about this method but reason why I don't want to use is is that I pref. handle every potential error on compile level if possible. I don't want write another unnecessary code what should happen when this object doesn't have this component and so on. However I am happy for any response and feedback, that is a reason why i posted this question. I like to hear other people opinion.
     
  9. MartinMa_

    MartinMa_

    Joined:
    Jan 3, 2021
    Posts:
    455
    Thank you for your response I will look in to this solution