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 does attaching objects to variables actually work?

Discussion in 'Scripting' started by PanRafal, Sep 7, 2023.

  1. PanRafal

    PanRafal

    Joined:
    Jun 6, 2023
    Posts:
    10
    In the official unity tutorial we have a line like this in the script:

    private PlayerController playerControllerScript;

    playerControllerScript = GameObject.Find("Player").GetComponent<PlayerController>();


    PlayerController is a script, so the way I see this declaration it means we make a variable that will have a script attached to it. But later in the same scipt I see this:

    playerControllerScript.GetComponent<Animator>().SetFloat("Speed_Multiplier", 0.5f);

    and this
    playerControllerScript.transform.position = Vector3.Lerp(startPos, endPos, fractionOfJourney);


    Its like variable that supposed to be only attached to a script has acces to any component of the Player object. And why I need GetComponent method to get to the Animator or "PlayerController" components, but its not required for transform?
     
  2. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    I'd be curious to know what official tutorial has this code, but that's a lot of bad practices going on!

    In any case it's about getting references (like a pointer) to an object before you can use it. Every game object will always have a transform component, so Unity already has that reference established for our convenience. For other components, you need to get a reference to those before you can use them.

    Though ideally you don't make repeated GetComponent calls where not needed. If this player component has an animator that needs to be accessed, reference that in the PlayerControllerScript via the inspector, and expose a C# property so it be accessed where needed.
     
  3. PanRafal

    PanRafal

    Joined:
    Jun 6, 2023
    Posts:
    10
    " reference that in the PlayerControllerScript via the inspector, and expose a C# property so it be accessed where needed."

    Can you elaborate on that?

    "For other components, you need to get a reference to those before you can use them."

    But how come variable that has "PlayerController" type can get a reference to an Animator type object? I just tried doing that in other project and I had an error. Why does this work there?
     
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Probably want to read the manual on Unity's component structure: https://docs.unity3d.com/Manual/Components.html

    It's pretty essential to using Unity.

    Code (CSharp):
    1. public class PlayerControllerScript : Monobehaviour
    2. {
    3.     [SerializeField]
    4.     private Animator _animator; // assign via inspector
    5.    
    6.     public Animator Animator => _animator; // can be accessed via other scripts
    7. }
    Like this. Assign a reference to the animator component via the inspector, that should be on the same game object as this component, and then if you have a reference to your player script, you can access the component without
    GetComponent<T>
    calls.

    You need to understand that
    GetComponent<T>
    will try to get a component on the same object that matches the generic parameter. You'll only actually get a reference if the component is actually there. If it's not there, you get
    null
    as a return value instead, and run into a null-reference exception.

    So in your example, it will only work if there is an Animator component on the same game object. If there isn't, you run into an error.
     
    Kurt-Dekker and Bunny83 like this.
  5. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    The only time I've seen things like this, was maybe a Brackey's beginner tutorial. As it was just to get you used to getting components.

    But realistically, 'playerControllerScript' should have all of it's components cached into it, as it would only do a quick GetComponent() upon initialization. In this particular case;
    public Animator myAnimator; 
    Awake() { myAnimator = GetComponent<Animator>(); }

    Therefor once you get that scripts reference, all you need is:
    playerControllerScript.myAnimator.SetFloat("Speed_Multiplier", 0.5f);

    But more realistically the script itself should only need one thing to tell it to do something, so you'd have a public function in it handle all it needs to with one input. looking more like:
    playerControllerScript.ModifySpeed(0.05f);

    As you can have multiple handles within that class, do everything it needs to, and not make 3-5 calls in the class that calls it. As I see a lot of beginners have GetComponent() calling the same thing multiple times, which not only is horrible practice, it's un-performant and unnecessary.
     
  6. PanRafal

    PanRafal

    Joined:
    Jun 6, 2023
    Posts:
    10
    Could you explane this line? Is it a lambda operator?

    Btw. Thanks for all the help I got so far.
     
  7. wideeyenow_unity

    wideeyenow_unity

    Joined:
    Oct 7, 2020
    Posts:
    728
    Yes, => is lambda.. which most importantly looks like an arrow from whence it came, which basically does, as it looks like.

    However, reading that again, looks like he might have meant:
    Code (CSharp):
    1. public Animator animator => _animator;
    As you never want to declare something new, the same way something is old. But the "_" normally denotes a private variable.

    Not saying you can't name a new variable with a capital letter, it just will confuse you, general rule of thumb and what-not..
     
  8. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Yes it's a lamba expression. It's just syntax sugar that inlines it as a read-only property.

    See below.

    No I meant it capitalised. I know Unity has lower-case public members, but my personal preference is capitalised public members. There's no real issue having them the same name as their type in some context either.
     
    Last edited: Sep 8, 2023
    PanRafal likes this.
  9. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,913
    Yes, you are correct about "variables" attached to gameObjects having access to all other components on the same gameObject. This works by giving them one link to the game object they're on (named
    gameObject
    ). This is a feature. It makes it so any link to any part of a gameObject is completely useful.

    playerControllerScript.GetComponent<Animator>()
    sees it's being run on a component so it knows to use the special link to jump up to the parent gameObect (which we know is "Player"), then does the normal thing where it searches for an Animator in the list. In other words, it automatically turns it into
    playerControllerScript.gameObject.GetComponent<Animator>()
    .

    An example of being useful, suppose you want a link to your current pet cat, which has a catPetScript on it. Write it as a link to that script:
    public catPetScript myCat;
    , and drag in the cat. You could have had myCat be a gameObject variable, but this way it's more obvious that cats go in there (and the Inspector won't even allow non-cats) and you don't need an extra line getting the script from the gameObject.
     
    PanRafal and spiney199 like this.
  10. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,503
    Statements like this confuse people.

    That's not a "variable". It's a property which is implemented as an expression-bodied member. It's also a standard convention to name properties using a capital letter.

    No, It's the lambda operator, but it's not a "lambda expression". The same symbol "=>" is used for two confusingly similar looking things.

    A lambda expression is an anonymous function with inputs on its left and an expression on the right. An expression-bodied member defines a member of a class (a method or property) by declaring it on the left and providing the expression which defines it on the right.
     
    CodeRonnie, PanRafal and spiney199 like this.
  11. PanRafal

    PanRafal

    Joined:
    Jun 6, 2023
    Posts:
    10
    One more thing. Im having a hard time interpretting this code:
    How do you assign animator via inspector if variable is private? Does serialization allow it to appear in the inspector? Why not just make it public?
    Later we got that lambda expression. Im not sure what it does in this example. I've read lambda construction is like this:
    (input-parameters) => { <sequence-of-statements> }
    and its just a nameless method. But in the line you made we don't have anything that would "take" returned value of this lambda expression, at least I dont see it. And what is the returned value, is it a pointer to the _animator? If im missing some knowledge I should have then tell me, I was trying to search for info in google, but couldn't find anything with line like that. Also why it will be accesable from other scripts now, without using GetComponent?
     
  12. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    5,769
    Have a read of this: https://docs.unity3d.com/Manual/script-Serialization.html

    [SerializeField]
    lets you serialise private fields, exposing them in the inspector so that you can assign references.

    We make it private, and then provide a read-only property because there's usually no reason for anything outside this object to be able to change this value. We just want them to be able to access and interface with it, but not change it. In doing so we reduce the chance of bugs happening from outside objects doing things we don't want them to do.

    It's a common pattern you'll pick up.
     
    angrypenguin and PanRafal like this.
  13. PanRafal

    PanRafal

    Joined:
    Jun 6, 2023
    Posts:
    10
    Thank you all.
     
    angrypenguin likes this.
  14. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,503
    You mark it with [SerializeField].

    Yes, that's exactly what it does. If you put the Inspector in Debug mode you'll see that Unity actually serializes it and shows any compatibly typed variables in the Inspector even if they're not marked. This is so that things don't get reset every time to change a script. (Anything of an incompatible type will be reset.)

    The [SerializeField] attribute tells Unity that you specifically want it to be serialized as a part of your scene, prefab, etc. configuration.

    That's a really good question!

    In short, making a variable public gives full read and write access to anything that can see it. This tends to lead towards messy and hard to maintain code, so things should be made as "private" as possible. Sometimes you want a thing to be readable by anything, but writable only internally, so you can wrap it in a property which only provides the "get" part, or where "set" is private.

    Properties are just function calls - one for get and one for set. They can have their access modifiers set individually, so you can have a public get and a private set, for instance. In C# they're made to look pretty, in other languages they're often just implemented as normal functions.

    You'll often see people write a private variable and then immediately provide full public access via a property, and think it's a bit redundant. The reason behind that is to keep users of the object away from the "implementation details". If I have a car and I need to know it's speed, I usually shouldn't have to care about whether it's a variable I'm reading, or a calculation which is being run, or maybe a cached value updated under some circumstances or whatever. The idea of making everything a property by default is that code written to use it now doesn't have to be updated if the implementation details change later. This does have a cost, because they are still functions being called, but that cost does not matter 99.5% of the time.
     
    Last edited: Sep 8, 2023
    CodeRonnie likes this.
  15. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,503
    "_animator" is already a pointer or, as C# calls them, a "reference".

    In a computer every single object is stored at a physical location in memory. We can refer to any object by knowing its location. So one object can refer to another by storing the location of that other object in a variable. That's what's happening here.

    See the link in my previous post. You're absolutely right that this isn't a "lambda expression" for precisely the reason you say - there are no inputs on the left.

    It's pretty common in programming for a symbol to mean different things in different contexts. It's a bit confusing at first. In this case, the lambda operator is being used to define an "expression-bodied member", which is basically of the form:
    Code (csharp):
    1. <method or property> => <expression with matching return type>
     
  16. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,503
    When you call "myGameObject.GetComponent(...)" on a GameObject, you're calling a function on the GameObject which looks through each of its attached Components and returns a reference to one where the type matches.

    Inside your script, you can save that reference to a variable. For instance...
    Code (csharp):
    1. Animator myAnimator;
    2.  
    3. void Start()
    4. {
    5.     myAnimator = GetComponent<Animator>();
    6. }
    Similarly, you can populate that variable in the Inspector by dragging the Animator in there. That tells Unity that when it instantiates the GameObject it needs to put a copy of the reference to the Animator it creates in that variable.

    Either way, as long as that Animator exists it will be in the same place in memory, so instead of "finding" the reference every single time, you can just keep a copy and re-use it.

    This is a really good use case for a property which only provides a 'get' function. You don't want to make the variable public because then anything can change the Animator reference and break it - that's bad! But you do need other stuff to be able to access it, and a property with a get function does that nicely.

    You might find that you have some reason to provide a private 'set' function in there. For instance, maybe your Animator changes from time to time, and you want to raise an event when that happens.

    Another common use for 'set' properties, whether public or private, is to sanitise the values being accepted. Say I have an input variable which needs to be between 0 and 1, but I want anything to be able to set it. The following code would do that nicely:
    Code (csharp):
    1.  
    2. private float input = 0;
    3.  
    4. public float Input
    5. {
    6.     get { return input; }
    7.     set { input = Mathf.Clamp01(value); }
    8. }
    This way I can still allow anyone to set values to my input variable, while being confident that it will always stay within the valid range.
     
    spiney199 likes this.