Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

How to reference an arbirtary script? (Serialize script component names)

Discussion in 'Scripting' started by Desprez, Jul 1, 2015.

  1. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    Say I have a script that's intended to be modular, and that script is going to send a value to another script.

    Since it's modular, I can't guarantee the name of the target script. (but say I CAN guarantee the target variable name)

    Ideally, I'd like to be able to type or drag the target script into an inspector field, and somehow convert that in Start() to the proper reference.

    As an example:
    Say one project needs to find targets for a weapon, and another needs to find targets for navigation.
    I can make one FindTarget script that will send the found target to the Weapon or Navigation script, and I can use the same code for both projects, or for any project that needs FindTarget functionality.

    But there's an infinite number of potential names that a target script might have, so how do I make that a variable that can be serialized, so the underlying code doesn't have to be edited?

    I suppose, I could do the reverse, where Weapon and Navigation grab the value from FindTarget, but that's the same issue, because now what if I create a different targeting script, say, AdvancedFindTarget.
    In the first example, I can simply replace the old one, and change the reference where the target is being sent.
    But if another script is going to grab the target, then things are messier as many different scripts might need to be changed if a new targeting script is used.

    I suppose this is also a coding best practices question as well.
    Should scripts generally send data to another script, or should they generally grab data from another script?
    It seems like grabbing what it needs is probably better, even if this specific example suggests sending is better.
     
  2. Boz0r

    Boz0r

    Joined:
    Feb 27, 2014
    Posts:
    419
    I think what you're looking for is a Strategy Pattern, where you make an interface to the functionality you want to use, and make different subclasses, calling the functions on the interface. So you'd have something like this:

    Code (CSharp):
    1. public class Something() {
    2.    
    3.     public ITargetingStrategy TargetingStrategy {get;set;}
    4.    
    5.     public void DoSomethingInvolvingTargeting() {      
    6.         var target = TargetingStrategy.FindTarget();
    7.     }  
    8. }
    And you'd make a couple of subclasses implementing the interface.
     
  3. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    I'm not so sure that's what I'm looking for. I'm working in Unityscript, so C# is a little hard for me to read.
    But I'm reading up on strategy patterns, and I don't THINK that's what I'm after. I'm still trying to wrap my head around how I'd use them in the context of various projects.

    It sound a little like you're suggesting I replace multiple scripts with different strategies.
    This isn't really an option, as the scripts might come from various projects and I don't want to include specific functionality unless I specifically include that script.
     
  4. Zaladur

    Zaladur

    Joined:
    Oct 20, 2012
    Posts:
    392
    Interfaces are definitely what you want.

    Code (csharp):
    1.  
    2. public interface ITargetHolder {
    3.     Transform Target {get; set;}
    4. }
    5.  
    your navigation/weapon/whatever classes that want to have targets just need to implement this interface, such as....

    Code (csharp):
    1.  
    2. public class WeaponTargetter : MonoBehaviour, ITargetHolder {
    3.  
    4.     private Transform _target;
    5.     public Transform Target
    6.     {  
    7.         get
    8.         {
    9.             return _target;
    10.         }
    11.         set
    12.         {
    13.             _target=value;
    14.         }
    15.     }
    16.    
    17.     //other methods here
    18.    
    19. }

    Finally, your generic FindTarget script that you want in all projects:
    Code (csharp):
    1.  
    2. public class FindTarget : MonoBehaviour {
    3.  
    4.     public ITargetHolder targetHolder;
    5.     public Transform target;  
    6.  
    7.     public void SetTarget(){
    8.                //run logic to determine your target, then pass it to the unknown object that needs it
    9.  
    10.         targetHolder.Target = target;  
    11.     }
    12. }

    Now you can include only the ITargetHolder and FindTarget scripts in every project you want, and the findTarget script can pass its target to any arbitrary thing with a target variable, AS LONG AS it implements ITargetHolder.

    Edit: I forgot that you can't use interfaces in the inspector for Unity. There are a couple workaround for that if you need it settable in the inspector, such as creating an abstract base class that acts as the interface, setting the targetHolder variable via scripts. If you need help with that down the road, just holler, but interfaces are definitely extremely appropriate for the functionality you are describing.
     
    Last edited: Jul 1, 2015
  5. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    Ok, Thanks for the responses. I've got some studying to do then.

    When I was looking up strategy patterns in javascript, one source said that javascript had a way to do it without interfaces, but that I could implement interfaces if I wanted to. So am I still looking for interfaces?
     
  6. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Interfaces are the simplest way to do this. You can't directly serialise interfaces out of the box, but you can still use things like GetComponent on an interface.

    The other is a full blown reflection scheme. You can basically iterate through the methods available on any class to see if it has one that matches the signature you are after. This is how Unity calls things like Update without resorting to inheritance and override. Reflection is a complex topic, if you are still getting to grips with interfaces then you probably don't want to go down this rabbit hole just jet.

    Also note that JavaScript used in Unity is not even close to real JavaScript.
     
  7. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    I realize that unityscript isn't javascript.
    It's starting to look like I'm going to need to learn C#, and I'm not too thrilled at the prospect of starting over.

    I initially choose javascript because I had some experience with it, and could jump into the syntax faster. But as I come up upon the more advanced things, more and more often I'm seeing javascript as the wrong tool.
    And by 'advanced', I really mean, 'just normal things' you'd want your code to do. Not to mention, the editor support looks better for C#

    I'm already seeing some potential namespace issues because javascript doesn't use them.
     
  8. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    @Zaladur
    I've got a million questions about the code you posted, but I think most of them are syntax related, and unfamiliarity with C#.

    For example:
    Code (csharp):
    1. public interface ITargetHolder {
    Ok so this defines a type of interface. Why is there a capital I prefixing TargetHolder? Because it's an interface?
    Code (csharp):
    1.     Transform Target {get; set;}
    2. }
    Hold on, this whole line is confusing.
    Why is Target capitalized if it's a variable of type Transform? If it's a method, what's with the curley brackets instead of parentheses? Why aren't get and set capitalized? Are they variables, keywords, or methods?

    Why is it even a Transform instead of a GameObject in the first place? Sure, I'll need transform information of a target, but most of the things I'll need to know about a target are going to come from an attached script of the GameObject. Either way, I'm going to have to get the Transform from the GameObejct, or the GameObject from the Transform. And it seems easier to read thinking of the target as a GameObject.

    Code (csharp):
    1. public class WeaponTargetter : MonoBehaviour, ITargetHolder {
    Ah, a class. I've used classes. But I've never seen them typed as MonoBehavior before. Why? It this just formality? What else would it be typed as?
    Then there is a comma. Ok, that's new. So, this is somehow attaching ITargetHolder to the class?
    So, WeaponTargetter is a class that must implement ITargetHolder. Ok, I see that.
    But, WeaponTargetter doesn't appear to be implemented anywhere?
    Code (csharp):
    1.     private Transform _target;
    2.     public Transform Target
    3.     {
    4.         get
    5.         {
    6.             return _target;
    7.         }
    8.         set
    9.         {
    10.             _target=value;
    11.         }
    12.     }
    13.    
    Ok, here's the Target method(?) again. With curly brackets. And there's get and set. They look like methods but aren't capitalized.
    But the verbiage seems counter intuitive.
    If I'm a weapon targeter and I want to get a target, why is it returning the variable? Shouldn't 'get' be defining the value of _target? When I would 'set' the target, wouldn't that be where I would return the value?
    Code (csharp):
    1.     //other methods here
    2.  
    3. }
    What other methods? What else would a weapon targeter do besides, well, target a weapon?

    Code (csharp):
    1. public class FindTarget : MonoBehaviour {
    2.  
    3.     public ITargetHolder targetHolder;
    4.     public Transform target;
    5.  
    6.     public void SetTarget(){
    7.                //run logic to determine your target, then pass it to the unknown object that needs it
    8.  
    9.         targetHolder.Target = target;
    10.     }
    11. }
    I still don't see WeaponTargetter implemented anywhere.
    Why does WeaponTargetter include , ITargetHolder in the first line, while FindTarget uses it as the targetHolder variable type?

    So somewhere in my project I'm going to implement:
    myTarget = FindTarget.SetTarget();
    Is that correct? And that's going to run a default targeting routine? But that's not very descriptive, as that could mean anything.
    Shouldn't the default be something like:
    myTarget = FindTarget.Closest();
    Am I'm reading this correctly?

    Then I can decide to extend upon this with more targeting options provided in another script.
    Which would then give me new options like.
    FindTarget.Furthest();
    FindTarget.MostHurt();

    Wait. Something doesn't seem right here.
    I still don't see how the target gets back to the unit script, because all this targeting is still being done in the targeting script.
    Unless you're saying to call FIndTarget directly from the unit script. But the whole point was that the unit script doesn't have any targeting functionality unless the targeting script is attached. So I still have the problem of how to send the target to the unit without knowing what the target script is.

    But that's the thing! How can I rely on an unknown script implementing ITargetHolder?
    If I can guarantee ITargetHolder, then why not just use an extended class in the first place?

    Unless you're saying that there's no way around this, and any unknown script would simply have to be modified to grab the target value from the targeting script, and also provide the targeting script with any other values it might need for some of the more advanced targeting functions, such as angle restrictions of unit doing the targeting. But that seems messy. How else would the targeting script grab these values from the unit if the script is unknown?
    (In the case where the unknown unit can't provide the needed values, FindTarget would do without, or use a default value. But it still has to know where to look.)

    The only other way I see is that FindTarget must always be a part of the unit script itself.
    So what I really need to do is come up with a generic unit script, and try to imagine all the features it might need in the future? That doesn't seem to be much of a maintainability win.

    I must be seriously confusing something here.
     
  9. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Convention. People in C# write I before an interface. It doesn't need to be there at all, but its what everyone does. It will make reading other peoples code, and having other people read your code, simpler.

    Its a property. Properties are like fields, but instead of just storing a value, they do some logic on it. One way to think of properties is as methods that look like fields from the outside.

    C# requires you to defined your classes explicitly. UnityScript does it for you. MonoBehaviour is for everything that needs to attach to a GameObject.

    You can inherit from nothing at all (which defaults to system.object). Or you can inherit from pretty much any class you like. Inheritance is one of the really important bits of OOP to understand.

    The , means that the class implements the interface.

    get is what happens when another script calls your variable. So if you go WeaponTargeter.Target then the code in get runs.

    set is what happens when another script assigns your variable. So if you go WeaponTargeter.Traget = someTransform the code in set runs.

    *****

    Basically with the set up described, any class that implements ITargetHolder will have a public variable called Target that you can access. One of the beauties of OOP is called polymorphism. You can treat any class that implements ITargetHolder as an ITargetHolder.

    The key advantage of an interface over inheritance is that you can use interfaces on completely unrelated classes where inheritance doesn't make sense.
     
  10. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    I've been reading through the basics of C#, and watching some tutorials.
    A lot of this is making more sense today.

    I'd say the big obstacle in most information is that there's little explanation as to WHY to use certain patterns, and what they are being used in place of, aside of very generalized statements that could mean anything.
    Something like, eventually when doing a, and b, you're going to run into problems x, y, and z. To avoid this, you can use <strategy>, and set it up like this, etc.

    So if a unit implements ITargetHolder, I can guarantee it will have a Target variable. ( Why is the variable Target capitalized? Because it's really a Property? )
    Ok, I can see the use of that, I suppose.
    But I still don't know how to tell what script it's in, unless I explicitly hard code in its class name. How do you access Target on a script name unknown when coding? Isn't there a way to serialize the script name?