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

Assigning a non-generic script to a generic script

Discussion in 'Scripting' started by AndrewStyan, Jun 11, 2021.

  1. AndrewStyan

    AndrewStyan

    Joined:
    Apr 6, 2020
    Posts:
    14
    Apologies for the title ... not sure how to express this problem.
    I have a generic script (the same script attached to multiple gameObjects) which needs to call a function on a script where the mechanism of that function is customised for each type of gameobject the generic script is attached to. The function name and parameters stay the same, the implementation varies (hence I've called it non-generic).
    I can't work out how to reference/link the two scripts. As the name of the non-generic script must change with each version of the implementation there seems to be no fixed way to reference it in the calling script, eg dragging the script onto a:
    public MyScript myScript;

    relies on a constant MyScript name for the script.
    I have tried dragging the script onto:
    public MonoBehaviour myScript = null;

    and referencing the function with:
    myScript.MyMethod();

    but this returns the error: 'MonoBehaviour' does not a definition for 'MyMethod' , etc....

    MyScript.cs looks something like this:
    public class MyScript : MonoBehaviour
    {
    public void MyMethod()
    {
    ..
    }
    }

    Is there something obvious/fundamental I am missing?
    Thanks
     
  2. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    The phrasing in your post is confusing, but I think what you're looking for is an Interface. You can use an interface to define a common API with customized functionality per implementation.

    First, define the interface:
    Code (csharp):
    1.  
    2. interface IUseable
    3. {
    4.    void OnUse();
    5. }
    6.  
    Then implement it on your various behaviours:
    Code (csharp):
    1.  
    2. class HealthPack : MonoBehaviour, IUseable
    3. {
    4.    public void OnUse()
    5.    {
    6.        Debug.Log("Healing the player");
    7.    }
    8. }
    9.  
    10. class TreasureChest : MonoBehaviour, IUseable
    11. {
    12.    public void OnUse()
    13.    {
    14.        Debug.Log("Giving the player some loot");
    15.    }
    16. }
    17.  
    And finally, you can invoke the method through the interface:
    Code (csharp):
    1.  
    2. class MyScript : MonoBehaviour
    3. {
    4.    private void Update()
    5.    {  
    6.        if(Input.GetKeyDown(KeyCode.Space) == true)
    7.        {
    8.            IUseable useable = targetGameObject.GetComponent<IUseable>();
    9.            useable.OnUse();
    10.        }
    11.    }
    12. }
    13.  
    The only catch is you can't serialize interfaces, so you can't do
    Code (csharp):
    1.  
    2. public IUseable useable;
    3.  
    And drag it in the inspector, but you can still use GetComponent to fetch it in Awake or Start() from a linked GameObject.
     
    Vryken likes this.
  3. AndrewStyan

    AndrewStyan

    Joined:
    Apr 6, 2020
    Posts:
    14
    Thanks @GroZZleR ! Sorry for the phrasing, not experienced enough to know the right terminology... but learning.

    I think I understand. A couple of questions though:
    - are the two behaviors (Healthpack, Treasurechest) actually on separate scripts which are then linked selectively later using GetComponent? Otherwise how does the calling script 'specify' which behavior?
    - where (in what script) is the interface defined? Does it matter?

    Thanks again.

    Will take a couple of days before I can try it out.
     
  4. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    897
    if there are multiple monobehaviours on a gameobject which implement a specific interface, GetComponent<IMyInterface>() will grab the 1st one it finds, the top most one in the inspector. to get all components that implement that interface use GetComponents<IMyInterface>(). you can then run each interface iteratively in a loop.

    If you want one script to run before the other in this loop then you can manage the orders in the inspector (though this hints at code smell). Better yet you can have the scripts also implement IComparable<IMyInterface> and then just sort the collection you get form GetComponents before running them
     
  5. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    Healthpack and Treasurechest are two separate MonoBehaviour scripts in @GroZZleR's example, yes.
    They are "linked" (in a way) because they both implement the
    IUsable
    interface.

    You can imagine that these scripts are all attached to their own separate GameObjects, and you have a system in place where you can click on any "usable" object in the scene to "use" it.
    The thing is, you could have potentially hundreds of "usable" objects. A light switch, a chair, a window, a car, a hammer, etc.

    The beauty of interfaces here is that when you click on a "usable" object to "use" it, you don't need to care what the actual script is, just that you clicked on the object, and you "used" it.
    It allows you to simply write something like this:
    Code (CSharp):
    1. if(clickedObject.TryGetComponent(out IUsable usable)) {
    2.   usable.Use();
    3. }
    ...Instead of something like this:
    Code (CSharp):
    1. if(clickedObject.TryGetComponent(out Healthpack healthpack)) {
    2.   healthpack.Use();
    3. }
    4. else if(clickedObject.TryGetComponent(out Treasurechest chest)) {
    5.   chest.Use();
    6. }
    7. else if(clickedObject.TryGetComponent(out LightSwitch lightSwitch) {
    8.   lightSwitch.Use();
    9. }
    10. else if(clickedObject.TryGetComponent(out Hammer hammer)) {
    11.   hammer.Use();
    12. }
    13. //etc...
    You can define the interface in any plain C# file. *Doesn't matter where it is in your project.

    *As long as it's not defined inside of an Editor folder and you need to include it in your build.
     
    GroZZleR likes this.
  6. AndrewStyan

    AndrewStyan

    Joined:
    Apr 6, 2020
    Posts:
    14
    Thanks @Vryken , @JoshuaMcKenzie and @GroZZleR

    Long weekend here so will take a day before I get to test all this. Sounds like it will definitely do what i want.

    Any suggestions for a better title so that others may find this resource ? I spent a day googling for a solution and as a relative newbie didn't really know what to search for. I've come across Interfaces before but it didn't click with me what they were for.
     
    GroZZleR likes this.
  7. Vryken

    Vryken

    Joined:
    Jan 23, 2018
    Posts:
    2,106
    Maybe a better thread title would be "Implementing a common function across multiple scripts" or something along those lines.

    "Generic" is actually the name of another feature in C#. You can read about that here if you're interested:
    https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/types/generics
     
    AndrewStyan likes this.
  8. AndrewStyan

    AndrewStyan

    Joined:
    Apr 6, 2020
    Posts:
    14
    Problem solved using posts 2 and 5. Works great!