Search Unity

Question Is It Possible to Get a Generic Script Reference?

Discussion in 'Scripting' started by John-B, Dec 9, 2022.

  1. John-B

    John-B

    Joined:
    Nov 14, 2009
    Posts:
    1,262
    I'd need to get a reference to another script, but I'd like to be able to get Find it based on its tag, not its name. This is typically the way I get a reference to another script:

    Code (CSharp):
    1. TheScript myScript;
    2. myScript = GameObject.FindWithTag("MyScriptTag").GetComponent<TheScript>();
    I'd like to be able to find any script with the correct tag, and assign myScript to it. is this possible? Is there a generic reference I can use to define the type of myScript? I have two very similar scripts, and I'd like to be able to get a reference to whichever one is currently active.
     
    Last edited: Dec 9, 2022
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,938
  3. AnimalMan

    AnimalMan

    Joined:
    Apr 1, 2018
    Posts:
    1,164
  4. John-B

    John-B

    Joined:
    Nov 14, 2009
    Posts:
    1,262
    It's not finding the script that's the problem. It's assigning it to a variable. I'd like to do something like this (not actual working code):
    Code (CSharp):
    1. GenericScriptType myScript;
    2.  
    3. tObj = GameObject.FindWithTag("MyScriptTag");  // only one tagged script in the scene
    4.  
    5. if (tObj.GetComponent<Script1>())
    6.    myScript = tObj.GetComponent<Script1>();  // assign whatever's found to myScript
    7. if (tObj.GetComponent<Script2>())
    8.    myScript = tObj.GetComponent<Script2>();
    9.  
    I'd like other scripts to be able to reference the myScript variable the same way regardless of whether it's linked to Script1 or Script2. But I can only assign Script1 to a variable of type Script1, and Script2 to a Script2 variable. Something like this would avoid a whole mess of special cases.
     
  5. RadRedPanda

    RadRedPanda

    Joined:
    May 9, 2018
    Posts:
    1,648
    You're going to have to assign a type to myScript regardless, and depending on what you assign it, that will affect what you can even access. To allow it to access any Script Component on an object, you could just declare it as a MonoBehavior type, but that would only allow you to access the MonoBehavior methods and properties, as you can't guarantee that myScript is a specific type at any time.

    Can you give an example of what you're trying to use it with? If Script1 and Script2 inherit from the same script, then you can just type it as whatever the base class is. If they are both two completely separate entities, then I wouldn't even keep going down this route. You could technically get away with it by casting the object, but it's going to run into a lot of problems down the line.
     
  6. I'm not entirely sure what you want to do here, but wouldn't it be easier if you define an interface an using that as the type? Of course, then you will need to define the members you want to call on the scripts from different places as well.
     
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,539
    You're basically describing polymorphism.
    https://en.wikipedia.org/wiki/Polymorphism_(computer_science)

    In C# to be able to do this both Script1 and Script2 must share a base type. What you keep calling generic type... note this likely adds confusion for everyone here since "generic" refers to something different in C#. Specifically it's things like List<T> or SomeType<T> where the <T> is the generic. The correct term would be polymorphism.

    The simplest way to get polymorphism for Script1 and Script2 is to have them inherit from the same type.

    Code (csharp):
    1. public class SomeBaseType : MonoBehaviour
    2. {
    3.  
    4.     public virtual void SharedMethod() {}
    5.  
    6. }
    7.  
    8. public class Script1 : SomeBaseType
    9. {
    10.     public override void SharedMethod() { Debug.Log("Called Script1"); }
    11. }
    12.  
    13. public class Script2: SomeBaseType
    14. {
    15.     public override void SharedMethod(){ Debug.Log("Called Script2"); }
    16. }
    17.  
    18. //elsewhere
    19. var myScript = GameObject.FindWithTag("MyScriptTag").GetComponent<SomeBaseType>();
    20. if (myScript) myScript.SharedMethod();
    You can also say 'public abstract class SomeBaseType'. This marks the class as abstract and you can't instantiate/addcomponent SomeBaseType, but you can Script1/Script2. This is useful if SomeBaseType is abstract/generalized to a point where having a direct instance of it doesn't make any sense. Think like 'Collider' vs 'BoxCollider' or 'SphereCollider'... you don't generally have a shapeless collider, but the Collider base type lets you treat all Collider sub types polymorphically.

    Alternatively you can use an interface to do the same thing (which is what I believe @Lurking-Ninja is suggesting):
    Code (csharp):
    1. public interface IFooable
    2. {
    3.     void Foo();
    4. }
    5.  
    6. public class Script1 : MonoBehaviour, IFooable
    7. {
    8.     public void Foo() { Debug.Log("Fooed Script1"); }
    9. }
    10.  
    11. public class Script2 : MonoBehaviour, IFooable
    12. {
    13.     public void Foo(){ Debug.Log("Fooed Script2"); }
    14. }
    15.  
    16. //elsewhere
    17. var myScript = GameObject.FindWithTag("MyScriptTag").GetComponent<IFooable>();
    18. if (myScript != null) myScript.Foo();
    Note that in this interface version that myScript is typed as the interface and not as some type that inherits from UnityEngine.Object.

    This means that the unity test where you can compare a unity object to null or cast to bool does not exist for this type (the compiler susses out operator and casting methods from the type the variable is typed as). So checking != null is literally checking if not null. As opposed to the usual thing where unity checks if it's not null AND if it's destroyed.

    This interface approach is actually how the EventSystem works (the UI components). When you implement interfaces like IDragHandler, the EventSystem actually just pulls up all components on a target GameObject and calls the methods on this interface.
     
    Last edited: Dec 9, 2022