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. Voting for the Unity Awards are OPEN! We’re looking to celebrate creators across games, industry, film, and many more categories. Cast your vote now for all categories
    Dismiss Notice
  3. Dismiss Notice

Why Can't I set a fields value to = a value type

Discussion in 'Scripting' started by SprayNpraY, Aug 30, 2018.

  1. SprayNpraY

    SprayNpraY

    Joined:
    Oct 2, 2014
    Posts:
    156
    Hi I've tried googling this but I can't find the answer even though its probably very simple but I'm finding it confusing.

    I don't understand why I can't just set a field to = a value. I will post some of my code below to demonstrate my confusion:

    Code (CSharp):
    1.    if (horitzontalInput > 0)
    2.         {
    3.             scale.x = 1;
    4.             posititon.x = 0.08f;
    5.             spriteRenderer.transform.localScale = scale;
    6.             spriteRenderer.transform.localPosition = posititon;
    7.         }
    I don't understand why I can't just put spriteRenderer.transform.localScale = 1
    but instead I have to make it = a variable with that value. Can someone please explain why

    Thanks
     
  2. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    They're different types, localScale is a Vector3, and the literal '1' is an integer.
    An implicit conversion does not exist, because it doesn't make any sense.

    That said, the right-hand-side needs to be a Vector3 (hard-coded, a variable, a return value of a method or the result of any other expression's evaluation). Same applies to any type that is either explicitly or implicitly convertable to Vector3.
     
    SprayNpraY likes this.
  3. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,833
    So, as Suddoha says, one issue you may be running into is types. To use your specific example, you can't say
    spriteRenderer.transform.localScale = 1;

    because localScale is a Vector3 and "1" is an int. But you can say:
    spriteRenderer.transform.localScale = Vector3.one;

    Vector3.one is a shortcut for the vector (1, 1, 1)

    But I think what you probably meant to ask is why you can't say:
    spriteRenderer.transform.localScale.x = 1;

    And instead have to do annoying workarounds like:
    Code (CSharp):
    1. Vector3 scale = spriteRenderer.transform.localScale;
    2. scale.x = 1;
    3. spriteRenderer.transform.localScale = scale;
    The reason for that is that "localScale" is not actually a variable; it's a property. Properties are things that look like variables but are actually functions. Behind the scenes, the compiler is turning your code into something analogous to this:
    Code (CSharp):
    1. Vector3 scale = spriteRenderer.transform.GetLocalScale();
    2. scale.x = 1;
    3. spriteRenderer.transform.SetLocalScale(scale);
    You can't set localScale.x directly because there is no "SetLocalScaleX" function.

    Properties exist because lots of times a class wants to put some restrictions on what outside people can do with a variable, or do something extra when that variable changes. (In this case, it's probably telling Unity that it needs to recalculate stuff for this object next frame instead of just using the same values as last frame.) Properties let you mostly pretend that it's just a variable even when more-complicated stuff is going on under the covers.

    If you want, you can sort of create your own "SetLocalScaleX" function using extension methods. But C# doesn't support extension properties, so the syntax won't be as nice as you might hope.

    If you wanted to go that route, you'd define a class like this somewhere in your project:
    Code (CSharp):
    1.     public static class MyVector3Extensions
    2.     {
    3.         public static void SetLocalScaleX(this Transform trans, float x)
    4.         {
    5.             Vector3 scale = trans.localScale;
    6.             scale.x = x;
    7.             trans.localScale = scale;
    8.         }
    9.     }
    And then you can call it like this:
    spriteRenderer.transform.SetLocalScaleX(1);


    Of course, you'd need to define separate functions for X, Y, and Z. And then define a whole new set of X/Y/Z functions for position, localPosition, localEulerAngles, ...there's a lot of Vector3 properties in UnityEngine. But you could do it if you want.
     
    SprayNpraY and Owen-Reynolds like this.
  4. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,914
    To echo Antistone, it's basically a bug in C#, caused by properties mixed with structs. If you write it without a property for position, the problem is clear: transform.position().x=1; is obviously assigning to the return value of a function, which is nonsense.

    But, the thing is, Unity created shortcuts for these (the ones in the bottom half of A's post). In Unityscript, transform,position.x=1; was fine (but not any faster). That language was discontinued, but at first everyone used it.

    I'm also not sure on this, but I think the reason Unity has position as a property is because the C# is really copying it to/from the C++ backend, which needs a function. They aren't making coding more difficult for no reason.
     
  5. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    3,915
    While sometimes annoying it is definitely not a bug, it is by design to prevent you from creating bugs. Through the property you receive a COPY of the value type. You could modify that but this is not what you want because the change will not propagate to the original struct the property returned as a copy.
     
  6. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,914
    I'm sorry about not being clear: transform.position.x=1; not working is the problem. C# just happens to have this weird very confusing special case (setting though a struct with get) that they could't fix by changing rules. The best they could do was to slap that "use a temp" error on top.

    It's a big deal, when trying to understand a language, to know what was a purposeful design choice, and what's awkward and messy because it just had to be to make other stuff work.
     
  7. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,833
    If they used a class instead of a struct, then it would be syntactically legal to set a subfield directly, but doing so wouldn't call the setter code on the main property, so if we need that code to run that would actually be worse.

    One might argue that the C# language should automatically expand something like "transform.position.x = 1" into something that calls both the getter and the setter on "position", but I have trouble imagining how that could possibly work in the general case where you might have properties inside of properties inside of properties to any level of nesting. At best, it seems like a rich source for confusing bugs.

    In any case, this problem really isn't any worse than languages that don't have properties at all. If this were C++, you'd have explicit GetPosition() and SetPosition() functions instead of something that looks syntactically like a field, but the limitations would be the same.
     
  8. Suddoha

    Suddoha

    Joined:
    Nov 9, 2013
    Posts:
    2,824
    Sounds as if you had a better way to design it. If so, I'm curious how that would look like / how it's supposed to work, if you say the not-allowed assignment is "the problem".

    It's some sort of restriction that can be found in other languages as well.
     
  9. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    Really, the only confusing thing here is the property looks like a class member variable.

    If the code had instead read:
    transform.GetPosition().x=1;


    Then I would NOT expect the x value to get saved back into transform's position.
     
  10. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,914
    Exactly (which is why I also wrote it). The important thing is that if someone _thought_ position().x=1 was legal, they'd be misunderstanding an important computer rule (L-values). They should read about it. But, as everyone here seems to agree, Unity's transformation.position.x= problem isn't like that. It's just an unfortunate messy bit. In other words, the OP wrote "it's probably very simple." No, it's seriously oddball.

    As far as setters on the whole part running on setters on fields, I think Swift may actually do that. They have some sort of "observer" setter (I skipped that section, since I don't use gets/sets.) Swift seems like Apple's answer to C#.
     
  11. SprayNpraY

    SprayNpraY

    Joined:
    Oct 2, 2014
    Posts:
    156
    Thanks for your replies,

    That makes sense, what is still confusing is that I can set

    Code (CSharp):
    1.         Vector2 scale = swordArcSprite.transform.localScale;
    2.             scale.x = 1;
    3.  
    As scale is of type vector 2, I know the x is a float. From what you're saying I don't understand how this is different:
    Code (CSharp):
    1. swordArcSprite.transform.localScale.x = 1;
    I get an error message saying: Cannot modify the return value of Transform.localScale because it is not a variable.

    I initialize it earlier in my start method:

    Code (CSharp):
    1.         swordArcSprite = transform.GetChild(1).GetComponent<SpriteRenderer>();
    2.  
    And I know it's of type SpriteRender but how come I can set the .x of scale when it is of type vector 2 but can't do the same with the .x of locale on the:

    Code (CSharp):
    1.             swordArcSprite.transform.localScale.x = 1;
    2.  
    as the .x for both is of type float or is it to do with how the memory storage of the variable type with a vector 2 and a SpriteRenderer works differently?

    I hope what I'm trying to ask makes sense.

    Thanks for your reply it has helped however even though I understand to a degree the basics of fields and properties and how to apply them I just can't wrap my head around what I have mentioned above and how just because its a property makes it act the way it does.
     
  12. Prastiwar

    Prastiwar

    Joined:
    Jul 29, 2017
    Posts:
    125
    Property getters returns value of variable, not variable itself
    Code (CSharp):
    1. public Vector3 GetPosition() {
    2.     return position; // returns copy value (not variable) of position
    3. }
    You can't do transform.position.x = 1; because its just getter, returns copy of value vector position, not variable.

    Code (CSharp):
    1. void Method() {
    2.     // Gets value (not initliazing variable)
    3.     GetPosition().x = 1; // can't assign because you have never initialized any variable
    4. }
    5. Vector3 GetPosition() {
    6.     return new Vector3(); // returns empty vector value
    7. }
    We can't change value of value - we can change value of variable

    In this case
    Code (CSharp):
    1. Vector2 scale = swordArcSprite.transform.localScale; // assign value of sword's sprite scale to 'scale' variable
    2. scale.x = 1; // we can change variable, notice it will not affect the scale of sprite
    3. swordArcSprite.transform.localScale = scale; // use setter of property to assign edited value
    In C#7 you can do

    Code (CSharp):
    1. private Vector3 pos;
    2.  
    3. private void Method() {
    4.     GetPosition().x = 1; // you can do that, why? Because GetPosition returns reference (we can say - variable)
    5. }
    6.  
    7. private ref Vector3 GetPosition() {
    8.     return ref pos;
    9. }
     
    SprayNpraY likes this.
  13. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    It boils down to this: Learn the difference between structs and classes in C#.

    Vectors are structs. Structs are value types in C#. When you assign to a value, you make a copy of that value. Let's put it this way:

    Code (csharp):
    1.  
    2. int x = 5;
    3. int y = x;
    4. y = 1;
    5.  
    What's the value of x after that? Still 5. y was originally a copy of x, then changed to 1. x is unaffected. Ints are value types. So are vectors.

    Now, let's use a Vector instead of an int:

    Code (csharp):
    1.  
    2. Vector2 v1 = new Vector2(5,5);
    3. Vector2 v2 = v1;
    4. v2.x = 10;
    5.  
    What's the value of v1.x? Still 5. This is because v2 was a copy of v1, but is not a reference to v1. So changing anything in v2 has no effect on v1.

    When you get an error message on this line:

    swordArcSprite.transform.localScale.x = 1;


    That's actually stopping you from making a silly mistake. What's actually happening here is you get a copy of localScale, then change the x value of that copy, then the copy is discarded. In other words: Nothing useful happens.

    Now, when you say that this works:

    Code (csharp):
    1.  
    2. Vector2 scale = swordArcSprite.transform.localScale;
    3. scale.x = 1;
    4.  
    That's actually incorrect. It only looks like it's working, but you're actually modifying a copy of the scale. However, because it's broken up into two lines, the compiler can't prevent you from making a mistake because it has no way of knowing whether this was intentional or not (unlike the single line example above).

    Not convinced? Try this:

    Code (csharp):
    1.  
    2. Vector2 scale = swordArcSprite.transform.localScale;
    3. scale.x = 1234;
    4. Debug.Log("localScale.x = " + swordArcSprite.transform.localScale.x);
    5.  
    See what kind of value it prints out.

    So what's the proper way to change the scale? Assign localScale like so:

    Code (csharp):
    1.  
    2. Vector2 scale = swordArcSprite.transform.localScale;
    3. scale.x = 1;
    4. swordArcSprite.transform.localScale = scale;
    5.  
    That's why sometimes in various code around the internet, you see local Vector2/Vector3/Quaternion instances that get modified, then assigned to the transform properties.
     
  14. SprayNpraY

    SprayNpraY

    Joined:
    Oct 2, 2014
    Posts:
    156
    Thank you both, again I understand the basis of what you're both saying I understand the basics of getters and setters and reference vs value types there is something else I must be missing as I'm still not grasping it.

    I understand if the .x is a value type but in this instance id of thought it's still classed as a reference type as its changing a value linked to the SwordArcSprite. I think I'd need a visual representation of what is happening in memory as I'm a very visual person.

    I'm trying to visualize in memory what is happening when I'm trying to do the
    Code (CSharp):
    1. swordArcSprite.transform.localSc
    2. ale.x = 1;
    In the memory is the x that I was trying to assign not changing the value with of a reference type of swordArcSprite but instead is it temporary in the stack trying to assign the value of x to = 1 which wouldn't do anything worthwhile hence the error?

    It'll probably randomly click for me and think why didn't I get this before, that usually happens for me with programming concepts I don't understand.
     
  15. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    You're still thinking about the x value. I'm saying localScale is what's tripping you up here.

    Let's break this line down: `swordArcSprite.transform.localScale.x = 1;`

    swordArcSprite = reference type
    transform = reference type
    localScale = property that gets/sets a value type
    x = value type

    Remember computers are dumb. Very very dumb. It cannot see the line as a whole. It must start from left to right, one step at a time.

    Its internal monologue would be like this:

    "Mmm, ok here's the sprite. And here's its transform. Excellent. Now for localScale... oh it's a property. OK let's call its get accessor. Oh, it was a struct? OK great, I now have a copy of the localScale. Mmmhmm what's next? Set its x value to 1? OK done. And.... huh? Nothing else? I'm supposed to throw the localScale copy away without doing anything with it? Now why in the world would you want to do that?"
     
    Last edited: Sep 7, 2018
    SprayNpraY likes this.
  16. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,914
    You're not missing some essential concept. It's really a glitchy interaction that can only happen when three funny things happen as once: a GET, for a struct, followed by an assignment to a field. As BlackPete wrote, the key is thinking of it as: transform.position().x=1;.
     
    SprayNpraY likes this.
  17. SprayNpraY

    SprayNpraY

    Joined:
    Oct 2, 2014
    Posts:
    156
    Thanks again for your help, I've rewatched some C# tutorials on reference vs value types, stack and heap which I'm understanding so now I realize why I'm so confused.

    I need to know how fields work in the stack and heap as I'm not understanding how it works in memory. I'm thinking with swordArcSprite being a reference type that everything that is changed linked to it would be changed in its reference in the heap. I know you have already addressed this but I followed and played around with some code from the tutorial which has either made me closer to understanding but at the same time even more confused.

    I have used the silly example from the tutorial to do with cows:

    Code (CSharp):
    1. public class Cow
    2.     {
    3.         int numSteaks;
    4.         public Udder udder;
    5.         public void ChangeUdderValue(int value)
    6.         {
    7.             udder.ouncesOfMilk = value;
    8.         }
    9.         public void printValueOfUdder()
    10.         {
    11.             Console.WriteLine(udder.ouncesOfMilk);
    12.         }
    13.     }
    14.  
    15.     public struct Udder
    16.     {
    17.        public int ouncesOfMilk;
    18.     }
    19.     class Program
    20.     {
    21.         static void Main(string[] args)
    22.         {
    23.             Cow cow1 = new Cow();
    24.             cow1.ChangeUdderValue(3);
    25.             cow1.printValueOfUdder();
    26.             cow1.udder.ouncesOfMilk = 5;
    27.             Console.WriteLine(cow1.udder.ouncesOfMilk);
    28.            
    29.         }
    30.     }
    the output:
    3
    5

    I was able to change the value of the struct and its int value in this cow example so I still don't understand why I can't do the same in the unity example with the swordarc.
     
  18. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,914
    Change udder to a get, just like position is a get: private Udder u; public udder { get { return u; } set { u=value; } }
     
    SprayNpraY likes this.
  19. SprayNpraY

    SprayNpraY

    Joined:
    Oct 2, 2014
    Posts:
    156
    Thanks again Owen I think I get it now after trying what you've suggested. I'm going to paste changesegs then say what I think is the reason then please correct me if I'm wrong. (note the last line doesn't work Ill explain why I put it there)

    Code (CSharp):
    1. public class Cow
    2.     {
    3.  
    4.         private Udder u;
    5.         public Udder udder
    6.         {
    7.             get { return u; }
    8.             set { u = value;}
    9.         }
    10.  
    11.         public Cow()
    12.         {
    13.             ChangeUdderValue(1);
    14.         }
    15.         public void ChangeUdderValue(int value)
    16.         {
    17.             u.ouncesOfMilk = value;
    18.         }
    19.      
    20.     }
    21.  
    22.     public struct Udder
    23.     {
    24.        public int ouncesOfMilk;
    25.     }
    26.     class Program
    27.     {
    28.         static void Main(string[] args)
    29.         {
    30.             Cow cow1 = new Cow();
    31.             Cow cow2 = new Cow();
    32.  
    33.             cow1.ChangeUdderValue(3);
    34.            
    35.             Console.WriteLine(cow1.udder.ouncesOfMilk);
    36.             Console.WriteLine(cow2.udder.ouncesOfMilk);
    37.             cow2.udder = cow1.udder;
    38. Console.WriteLine(cow2.udder.ouncesOfMilk);
    39.  
    40.             cow2.udder.ouncesOfMilk = cow1.udder.ouncesOfMilk;
    41.  
    42.  
    43.         }
    44.     }
    Output without the last line:
    3
    1
    3

    So from what I think I have figured out basically trying:

    Code (CSharp):
    1.             cow2.udder.ouncesOfMilk = cow1.udder.ouncesOfMilk;
    Is the equivalent of trying my original confusion:
    Code (CSharp):
    1. swordArcSprite.transform.localScale.x = 1;
    And the reason it doesn't work is because in memory the value of ouncesOfmilk or
    swordArcSprite.transform.localScale.x
    Is only stored in the stack and its trying to then change the value and change to something else in the stack that would immediately be removed and not storing the value because it isn't changing the value in the heap/memory (this is where the main part of my confusion came from as I thought since the value type is "linked" to the object it would change it in memory)

    were as doing:
    Code (CSharp):
    1.  cow2.udder = cow1.udder;
    or
    Code (CSharp):
    1. swordArcSprite.transform.localScale = scale;
    works because it's changing where the pointer is referencing, it's pointing to a different reference in memory so it gets the values of the other object which in turn changes the values. I hope what I'm saying makes sense.

    I haven't been able to find a visual representation of this exact process with getters and setters only with basic value and reference types. I still find it slightly confusing thinking pointing the value of one object in memory to another value of another object in memory would store it as its from a referenced object but I guess it doesn't work that way from what I'm starting to understand.
     
  20. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,914
    The problem also occurs in just cow1.udder.ounceOfMilk=4;. Your reason for it not working seems overly complicated. cow1.udder is a function call. Get's are really disguised function calls. Since udder is a struct, which is a value type, the get creates and returns a copy. It's roughly the same error as Mathf.Max(a,b)=10; -- trying to assign to a temporary function return value.

    cow1.udder = cow2.udder works because cow1 is using the SET, in the intended way. It's really cow1.setUdderTo(cow2.udder);.