Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question How to dynamically change the Type you are looking for at runtime?

Discussion in 'Scripting' started by MrKodei, Sep 27, 2023.

  1. MrKodei

    MrKodei

    Joined:
    Aug 8, 2021
    Posts:
    12
    Hey there,

    i want to dynamically change the Type of Component (my Custom Classes, such as IDamagable, IInteractable, ...) my "TryGetComponent" Method is looking for. Is there any way to do this?

    Code (CSharp):
    1. public class LineOfSight : MonoBehaviour
    2. {
    3.     public Type ComponentToLookFor;
    4.  
    5.     void Update()
    6.     {
    7.         RaycastHit hit;
    8.         if (Physics.Raycast(Origin.position, CastDirection * CastRange, out hit))
    9.         {
    10.             if (hit.transform.TryGetComponent(out ComponentToLookFor))
    11.             {
    12.                 Debug.Log("found" + ComponentToLookFor);
    13.             }
    14.         }
    15.     }
    16. }
     
  2. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    10,468
  3. MrKodei

    MrKodei

    Joined:
    Aug 8, 2021
    Posts:
    12
    Thanks mate! It worked. I guess i still don't really get the difference between Type and Component.
    Is there, by any chance, any Documentation that explains the difference casually?

    For anyone who's interested, here my code:
    Code (CSharp):
    1. public class LineOfSight : MonoBehaviour
    2. {
    3.     [Header("Values")]
    4.     public Transform Origin;
    5.     public Vector3 CastDirection;
    6.     public float CastRange;
    7.     public Type SearchableComponent;
    8.  
    9.     private void Start()
    10.     {
    11.         SearchableComponent = typeof(Enemy);
    12.     }
    13.  
    14.     void Update()
    15.     {
    16.         RaycastHit hit;
    17.         if (Physics.Raycast(Origin.position, CastDirection, out hit, CastRange))
    18.         {
    19.             if (hit.transform.TryGetComponent(SearchableComponent, out Component component))
    20.             {
    21.                 Debug.Log("found" + component);
    22.             }
    23.         }
    24.  
    25.     }
    26.  
    27.     [ContextMenu("ChangeLayerToLookForToPlayer")]
    28.     public void ChangePlayer()
    29.     {
    30.         SearchableComponent = typeof(Player);
    31.     }
    32.  
    33.     [ContextMenu("ChangeLayerToLookForToEnemy")]
    34.     public void ChangeEnemy()
    35.     {
    36.         SearchableComponent = typeof(Enemy);
    37.     }
    38.  
    39.     void OnDrawGizmosSelected()
    40.     {
    41.         Gizmos.color = Color.green;
    42.         Gizmos.DrawRay(Origin.position, CastDirection * CastRange);
    43.     }
    44. }
     
  4. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    303
    A "Type" is like its name suggests... a type of an object. Int, bool, string, List, Array, and in your case LineOfSight are Types.

    A Component is a Unity specific C# class (Type) that you can attach to a Game Object (in the Editor or at runtime if you want)

    In your example:
    LineOfSight class is a Unity Component (MonoBehaviour a.k.a Script in this case) of Type... LineOfSight.

    So by definition, all Unity Components are Types. But not all Types are Unity Components. For a Type to be a Unity Component it must inherit from Component. MonoBehaviour inherits from Behaviour, which inherits from Component. So MonoBehaviours are Components, and your classes that inherits from MonoBehaviours are Components too (like your LineOfSight):

    LineOfSight -> MonoBehaviour -> Behaviour -> Component.

    Also there are two types of Types (lol) in C#:
    - Value Types (int, bool, float, struct...)
    - Reference Types (all classes like: List, Array, Unity Components...etc)

    You can Google/ChatGPT to understand the difference between them.

    upload_2023-9-27_14-40-43.png
     
    Last edited: Sep 27, 2023
    zulo3d and MelvMay like this.
  5. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    280
    Although the docs display the signature of the first method like this:
    Code (CSharp):
    1. public bool TryGetComponent(out T component)
    It's actually this:
    Code (CSharp):
    1. public bool TryGetComponent<T>(out T component)
    It is a generic method that can utilize generic type inference. You can specify the type with either method, but you are not required to specify the type with the generic method because the compiler is able to infer the type based on the known type of the out argument you provide. That's probably why they don't include the <T> in the docs because they maybe want to encourage people to make use of generic type inference because it makes your code look cleaner. (Opinion.)

    So, you can try to get, for example, an Enemy component with either method like this:
    Code (CSharp):
    1. if(hit.transform.TryGetComponent(typeof(Enemy), out Component component))
    2. // Or
    3. if(hit.transform.TryGetComponent<Enemy>(out Enemy component))
    4. // But, you are not required to explicitly specify Enemy as the generic type, since the compiler already knows.
    5. if(hit.transform.TryGetComponent(out Enemy component))
    The problem with your first attempt was that you were trying to get a Component of type Type. Components are not objects of type Type. It is possible to use the generic method to dynamically handle different types, but the approach and the syntax are a little different. Here is an example that achieves your goal using the generic method.
    Code (CSharp):
    1.  
    2. public class LineOfSight : MonoBehaviour
    3. {
    4.     [Header("Values")]
    5.     public Transform Origin;
    6.     public Vector3 CastDirection;
    7.     public float CastRange;
    8.  
    9.     private Func<bool> CurrentRaycast;
    10.  
    11.     // You aren't required to cache the delegate instances like I do here, but I prefer not to instantiate a new delegate object each time.
    12.     // The expression body operator and null coalescing assignment operator used here may look foreign,
    13.     // but it's just a null check the first time, so it lazy loads a delegate instance to keep reusing when you toggle.
    14.     private Func<bool> _enemyCast;
    15.     private Func<bool> EnemyCast => _enemyCast ??= GenericRaycast<Enemy>;
    16.  
    17.     private Func<bool> _playerCast;
    18.     private Func<bool> PlayerCast => _playerCast ??= GenericRaycast<Player>;
    19.  
    20.  
    21.     // Note that this method cannot use generic type inference because it has no parameters.
    22.     private bool GenericRaycast<T>() where T : Component
    23.     {
    24.         if(Physics.Raycast(Origin.position, CastDirection, out RaycastHit hit, CastRange))
    25.             return hit.transform.TryGetComponent(out T component);
    26.         return false;
    27.     }
    28.  
    29.     private void Start()
    30.     {
    31.         CurrentRaycast = EnemyCast;
    32.     }
    33.  
    34.     void Update()
    35.     {
    36.         if(CurrentRaycast())
    37.             Debug.Log("Hit!");
    38.     }
    39.  
    40.     [ContextMenu("ChangeLayerToLookForToPlayer")]
    41.     public void ChangePlayer()
    42.     {
    43.         CurrentRaycast = PlayerCast;
    44.         // If you did not cache the delegate instance objects like I did above you could just do this.
    45.         // However, it will create a new object for garbage collection each time you toggle.
    46.         CurrentRaycast = GenericRaycast<Player>;
    47.     }
    48.  
    49.     [ContextMenu("ChangeLayerToLookForToEnemy")]
    50.     public void ChangeEnemy()
    51.     {
    52.         CurrentRaycast = EnemyCast;
    53.         // If you did not cache the delegate instance objects like I did above you could just do this.
    54.         // However, it will create a new object for garbage collection each time you toggle.
    55.         CurrentRaycast = GenericRaycast<Enemy>;
    56.     }
    57. }
    So, you see, you can use either method signature to achieve your intended goal, it's just a matter of using them properly. Use whichever style suits you best.

    Oh, and one other major difference is that with the generic methods you get the specific type you're looking for. Meaning, you will get an actual Enemy reference, or Player reference object. With the non-generic method that accepts a Type object argument, you will always get a base class reference to Component that would need to be upcast to a specific type if necessary.
     
  6. CodeRonnie

    CodeRonnie

    Joined:
    Oct 2, 2015
    Posts:
    280
    Also, just to hopefully clarify some of your confusion around "Type," there are types, and then there are Types. Type is a specific class defined in the .NET System library. The typeof operator that you used in your second, working example returns an object instance of the Type class.
    https://learn.microsoft.com/en-us/dotnet/api/system.type

    An instance of the Type class contains values internally that define it as either an Enemy or a Player, or whatever. But, the results of the typeof operator for both Enemy and Player are both just instances of the exact same type of class, Type! It's the values that are stored within those two different Type objects that differentiate them. So, when you pass a generic type argument of Type to a generic method, you're saying I want a Type (with no concern for the values held within that would define the type more specifically.) You're not saying I want and Enemy or a Player, just a Type, any Type will do. That's why the first attempt wasn't working, because a Component is not a Type, but it is a type!

    With generics you don't have to worry about the typeof operator or Type objects, but there are other idiosyncracies. Apples and oranges.
     
    Nad_B likes this.
  7. Nad_B

    Nad_B

    Joined:
    Aug 1, 2021
    Posts:
    303
    Like @CodeRonnie said, "Type" (System.Type to be precise) is itself a type :D since it's a class.

    I can totally understand how it could be confusing for beginners.
     
  8. MrKodei

    MrKodei

    Joined:
    Aug 8, 2021
    Posts:
    12
    Thanks very much you guys! I definitely understand the topic more now, even though i know i still have a lot to learn! I really appreciate you're time and insight :)
     
    Nad_B and CodeRonnie like this.