Search Unity

Why is there transform.localRotation.eulerAngles.Set if it doesn't do anything?

Discussion in 'Scripting' started by Johannski, Jan 23, 2016.

  1. Johannski

    Johannski

    Joined:
    Jan 25, 2014
    Posts:
    826
    Hi there,

    I was doing a simple rotation and I noticed the function transform.localRotation.eulerAngles.Set(x,y,z). Strangely it doesn't do what it says, the value just doesn't change. My fix was changing the quaternion instead.

    Code (CSharp):
    1. // Works
    2. gameobject.transform.localRotation = Quaternion.Euler(0f, 90f, 0f);
    3.  
    4. // Doesn't work
    5. gameobject.transform.localRotation.eulerAngles.Set(0f, 90f, 0f);
    Did I miss anything or did unity add a method that does nothing? I'm using Unit 5.3.1p3
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    it does do something

    the problem is that you're accessing structs through properties that are handing out copies.

    Note, structs are 'value types' rather than 'reference types'. And that properties are prettied up function that return results rather than allow direct access to memory.

    So, when you say a long chain of properties like:

    Code (csharp):
    1. gameObject.transform.localRotation.eulerAngles
    You're call 3 functions in a row...

    GetTransform()
    GetLocalRotation()
    GetEulerAngles()

    where the 2nd and 3rd are handing out structs (a quaternion and a Vector3 respectively).

    That Vector3 does not know it's representing a rotation. It's just a Vector3 that the Quaternion converted itself into when you called 'eulerAngles'. You set that Vector3 to new values, but it doesn't cascade back into the transform from which the entire chain originated.

    now try this instead:

    Code (csharp):
    1.  
    2. var rot = gameObject.transform.localRotation.eulerAngles; //get the angles
    3. rot.Set(0f, 90f, 0f); //set the angles
    4. gameObject.transform.localRotation = Quaternion.Euler(rot); //update the transform
    5.  
    Also note, unity has included an 'eulerAngles' and 'localEulerAngles' property directly on Transform to streamline this further:

    Code (csharp):
    1.  
    2. var rot = gameObject.transform.localEulerAngles;
    3. rot.Set(0f, 90f, 0f);
    4. gameObject.transform.localEulerAngles = rot;
    5.  
    Though of course, if you're just setting them... why not set it directly?

    Code (csharp):
    1.  
    2. gameObject.transform.localEulerAngles = new Vector3(0f, 90f, 0f);
    3.  
     
  3. Johannski

    Johannski

    Joined:
    Jan 25, 2014
    Posts:
    826
    Wow, that was very informative. That really does make sense, I thought something like that could be the problem, I just didn't see it. the localEuler is an interesting shortcut, thanks for pointing it out!
     
    tenconmar likes this.
  4. ajTanvirP

    ajTanvirP

    Joined:
    Sep 8, 2016
    Posts:
    1

    very good thank you... was stuck here
     
  5. Xonatron

    Xonatron

    Joined:
    Jan 14, 2013
    Posts:
    31
    What is the point of .Set()? And can you elaborate one why .Set() works but does not cascade backwards.

    I appreciate your help. I was stuck understanding the same thing:

    gameObject.transform.eulerAngles.Set(45.0f, 45.0f, 45.0f); // DOES NOT WORK
    gameObject.transform.eulerAngles = new Vector3(45.0f, 45.0f, 45.0f); // WORKS
     
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    See my previous post as to why Set doesn't work in the context you're using it.

    I already cover this.

    It's because the property transform.eulerAngles returns a copy of the Vector3 struct, you modify that copy, not the actual value.

    The Set method exists for when you have a Vector3 variable, not for when you access a Vector3 property.
     
    Xonatron likes this.
  7. Xonatron

    Xonatron

    Joined:
    Jan 14, 2013
    Posts:
    31
    lordofduct, I read your reply (thanks), I was lost at precisely where the attempted method fails.

    So, to be clear...

    gameObject.transform.eulerAngles.Set(45.0f, 45.0f, 45.0f); // DOES NOT WORK
    gameObject.transform.eulerAngles = new Vector3(45.0f, 45.0f, 45.0f); // WORKS

    ...in the top example, transform.eulerAngles returns a copy that is then accessed via the .Set() method, which therefore modifies the copy?

    Apologies, but this is confusing to me. I must be thinking of .Set() from the wrong perspective. It was never meant to be used this way.

    Can you show an example where .Set() would work?

    Thanks so much.
     
  8. BlackPete

    BlackPete

    Joined:
    Nov 16, 2016
    Posts:
    970
    Code (csharp):
    1.  
    2. var angles = gameObject.transform.eulerAngles;
    3. angles.Set(x, y, z);
    4. gameObject.transform.eulerAngles = angles;
    5.  
     
    Xonatron likes this.
  9. Xonatron

    Xonatron

    Joined:
    Jan 14, 2013
    Posts:
    31
    Thanks, BlackPete. I guess lordofduct did already show this example, so thanks for that too.
     
  10. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,537
    It all has to do with where the data exists in memory... and a flaw with the design of C# properties.

    So first off there are 2 main data types in C#:

    value types: structs, primitives, enums - these exist in specific location in memory, and the variable that is set to its value is that memory location. If you then set another variable to the same value, the value is copied, and each variable is distinct from one another. The idea is that a value does not have identity, it is not an object... it's a value. You don't "have a five", you instead "have the value of five".

    Code (csharp):
    1.  
    2. Vector3 a = new Vector3(0f, 1f, 0f);
    3. Vector3 b = a; //b is a copy of a, they are both set to <0,1,0>
    4. b.Set(1f,1f,0f); //now b is not equal to a, since it was modified
    5.  
    references types: classes, interfaces - these exist exclusively on the heap, and have object identity. Instead of copies, you reference these as objects. You "have a Transform", as opposed to "have the value of Transform". If the position of the transform changes for instance, the transform is still the same transform... just at a new position. The position is the value, the transform is the object with position.

    Code (csharp):
    1.  
    2. Transform a = gameObject.transform;
    3. Transform b = a; //b and a are the same object
    4. b.Translate(1f, 1f, 0f); //both b and a are updated, since b and a are the same object
    5.  
    ...

    Now, in regards to value types, when you call methods/functions on a struct, or you set a field of a struct... you're directly manipulating the memory location of that value.

    Code (csharp):
    1.  
    2. Vector3 v = new Vector3(0f, 1f, 0f);
    3. v.x = 5f; //you're directly modifying the memory location of 'v'
    4.  
    If you happen to have a class that has a Vector3 as a field, and you manipulate that, you are manipulating the memory location of the field:

    Code (csharp):
    1.  
    2. public class Foo
    3. {
    4.     public Vector3 position;
    5. }
    6.  
    7. Foo obj = new Foo();
    8. obj.position = new Vector3(0f, 1f, 0f);
    9. obj.position.x = 5f; //position has been updated... you directly accessed position
    10.  
    This all should make sense to you up to now, as this appears to be your assumption up to this point.

    So... why doesn't transform.eulerAngles not work???

    Well... 'eulerAngles' is not a field. 'eulerAngles' is instead a 'property'. A 'property' is actually a method/function that has the syntax of a field. Here is a very simple property:

    Code (csharp):
    1.  
    2. public class Foo
    3. {
    4.     private Quaternion _rotation;
    5.  
    6.     public Quaternion rotation
    7.     {
    8.         get { return _rotation; }
    9.         set { _rotation= value; }
    10.     }
    11. }
    12.  
    13. //allowing a syntax like this:
    14. Foo obj = new Foo();
    15. obj.rotation = Quaternion.Euler(0f,0f,0f);
    16.  
    Note we get to syntactically treat it like a field, but in actuality its a set of 2 methods. In actuality it really looks like this under the hood:

    Code (csharp):
    1.  
    2. public class Foo
    3. {
    4.     private Quaternion _rotation;
    5.  
    6.     public Quaternion Get_rotation()
    7.     {
    8.         return _rotation;
    9.     }
    10.     public void Set_rotation(Quaternion value)
    11.     {
    12.         _rotation = value;
    13.     }
    14. }
    15.  
    16. Foo obj = new Foo();
    17. obj.Set_rotation(Quaternion.Euler(0f,0f,0f));
    18.  
    This means that the 'rotation' you 'get' from this property is not a direct reference to the '_rotation' field inside the class. Instead it's a copy of the '_rotation' field inside. This is why it's a "copy" and you can't directly call 'Set' or anything on it.

    Thing is that properties can get really powerful, they don't necessarily have to uncover actual underlying data. But instead can be conversions of underlying data:

    Code (csharp):
    1.  
    2. public class Foo
    3. {
    4.     private Quaternion _rotation;
    5.  
    6.     public Quaternion rotation
    7.     {
    8.         get { return _rotation; }
    9.         set { _rotation= value; }
    10.     }
    11.  
    12.     public Vector3 eulerAngles
    13.     {
    14.         get { return _rotation.eulerAngles; }
    15.         set { _rotation = Quaternion.Euler(value); }
    16.     }
    17. }
    18.  
    In this case, accessing 'eulerAngles' is really returning a conversion of '_rotation' as euler angles. And when you set it, it sets '_rotation' with a Quaternion constructed from the passed in euler angles. But really the Quaternion this entire time is actually stored in memory as the 4 complex numbers that define it (x,y,z,w... which are not euler angles).

    ...

    FURTHERMORE, there is yet another layer of complication. The '_rotation' is not stored on the mono/.net/C# side of things in the engine. Rather instead, it's stored on the C++ side. So really, these properties could be better described as:

    Code (csharp):
    1.  
    2. public class Foo
    3. {
    4.  
    5.     private int _instanceId;
    6.  
    7.     public Quaternion rotation
    8.     {
    9.         get { return InternalEngine.GetRotation(_instanceId); } //this is a heavily simplified description of how this actually works
    10.         set { InternalEngine.SetRotation(_instanceId, value); }
    11.     }
    12.  
    13.     public Vector3 eulerAngles
    14.     {
    15.         get { return InternalEngine.GetRotation(_instanceId).eulerAngles; }
    16.         set { InternalEngine.SetRotation(_instanceId, Quaternion.Euler(value)); }
    17.     }
    18. }
    19.  
    So really, when you're accessing the 'eulerAngles' property. You're accessing a conversion, of a copy, of a field, that is transferred over from the C++ side of the engine.

    You do not have direct access to the value's location in memory... so therefore can not directly manipulate it.

    ...

    Funny enough, UnityScript allows it. But that's because it contextually recognizes that you're accessing a property, and uses syntax sugar (similar to how really a property is a set of a get and set methods) via the compiler to make it work under the hood. UnityScript can do this because Unity designed UnityScript, they get to decide this. C# on the other hand is defined by Microsoft, and they didn't add such functionality.

    This actually was a HUGE point of contention long before Unity was a thing, and C# was first defined, way back in 2000.

    At the time it was heavily compared to Java... because honestly, it borrowed a LOT from Java. But one thing it diverged from Java on was these 'properties'. In Java you just have a 'SetField' and 'GetField' method to encapsulate private fields (there are syntax extensions that complicate this some). The argument was made that the 'property' was a bad idea for the specific reason that it 'looks like a field, but acts like a method' and therefore may confuse people...

    ...just like it has confused you.
     
    liudgervr, thieum and Munchy2007 like this.
  11. Xonatron

    Xonatron

    Joined:
    Jan 14, 2013
    Posts:
    31
    lordofduct, wow thank you for the thorough reply. That is interesting. and I can see how it would be a point of contention when C# was created.
     
  12. L-Tyrosine

    L-Tyrosine

    Joined:
    Apr 27, 2011
    Posts:
    305
    Giving a little addition to the excellent explanation from lordofduct, with some are more areas that value x reference type needs attention:

    Suppose we got a class

    Code (csharp):
    1. public class Test: Monobehaviour
    2. {
    3.     public int Data;
    4. }
    and create it
    Code (csharp):
    1. Test test = gameObject.addComponent<Test>();
    now I set it to another variable
    Code (csharp):
    1. Test x = test;
    Of course, both x and test refers to same object. You don't created a new Test by doing this. So:
    Code (csharp):
    1. x.Data = 100;
    2. Debug.Log(test.Data); // will print 100;
    Class instances (objects) are reference types.

    Now, things are different for Value types:
    Code (csharp):
    1. Vector3 a = new Vector3(0, 0, 0);
    2. Vector3 b = a;
    3. b.x = 100;
    4. Debug.Log(a.x); // will print 0
    By using b = a, we a are "creating" a new separate entity, on a different memory location, with exact same values. C# do us a copy.
    This also happens when you pass it as a method parameter:

    Code (csharp):
    1. void SomeMethod(Vector3 vector)
    2. {
    3.     vector.x = 100;
    4. }
    5.  
    6. ...
    7.  
    8. Vector3 a = new Vector3(0, 0, 0);
    9. SomeMethod(a);
    10. Debug.Log(a.x); // will print 0!
     
  13. Xonatron

    Xonatron

    Joined:
    Jan 14, 2013
    Posts:
    31
    Got it. Thanks for the help breaking it down. I am familiar with pass by value and pass by reference, however it can be tricky when you assume one is happening instead of the other.