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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

What's the best practice for moving RectTransforms in Script?

Discussion in 'UGUI & TextMesh Pro' started by Gigiwoo, Aug 25, 2014.

  1. Gigiwoo

    Gigiwoo

    Joined:
    Mar 16, 2011
    Posts:
    2,981
    Final testing revealed MASSIVE performance hits from Tweening my UI controls. Profiling (Unity Pro) showed this was a result of setting the tranforms position: MyCoolUIGameObject.transform.position = Blah blah ... Which led me to assume that I should be moving my UI controls a different way. I'm inclined to use RectTransform.anchoredPosition, however, since I've already got it wrong once, I'm hoping someone smarter can guide me to best practices.

    TL;DR: What's the best practice for moving a RectTransform in C# Script?

    Gigi

    PS - Is it a bug (or expected behavior) that gameobject.transform.position is slow?
     
    L_Dude and SoCentral2 like this.
  2. Sander1991

    Sander1991

    Joined:
    Aug 25, 2014
    Posts:
    162
    I guess you wanna reposition your UI. Do it like that:

    using UnityEngine.UI;

    void Start()
    {
    GetComponent<RectTransform>().localPosition = your position;​
    }

    This should be it.
     
    zanatos90, codebey, BusyRoots and 2 others like this.
  3. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,744
    I don't think localPosition is the answer. RectTransforms have anchors, pivots, and anchored positions as their 'native language', and I suspect the reason that .position is slow is because it has to translate that position in weird ways up through the whole chain of parents until it gets some anchored positions it can use. localPosition avoids translating up the chain, but it's still 'unnatural' for the RectTransform to use it.

    anchoredPosition is probably the correct way to go. It is, if nothing else, the property that gets animated when you hit record and move UI objects around - that is to say, it's apparently Unity's first choice of what to change when it comes to tweening.
     
  4. Gigiwoo

    Gigiwoo

    Joined:
    Mar 16, 2011
    Posts:
    2,981
    Glad to see others with similar thoughts. Wonder what the devs have in mind...
    Gigi
     
    StormMuller likes this.
  5. Tim-C

    Tim-C

    Unity Technologies

    Joined:
    Feb 6, 2010
    Posts:
    2,184
    Anchored position is correct. If you are using pixel perfect we need to realign children elements so it can be slow. Is your canvas using pixel pefect?
     
  6. KickBack

    KickBack

    Joined:
    Nov 18, 2014
    Posts:
    10
    Did you ever find a solution to this? Animating UI elements is horribly slow on iOS at the moment. (not so much on android, weirdly enough? could it be a bug?)
     
  7. Gigiwoo

    Gigiwoo

    Joined:
    Mar 16, 2011
    Posts:
    2,981
    @KickBack - UI performance is still quite slow, though most of my work is still in 4.6.x. Unity knows about it. They've made some updates in 5.x. Our initial tests didn't show they made much difference.

    TL;DR - Complex UI's are easy to build in Unity, behave wonderfully, and are quite slow.

    Gigi
     
    Last edited: Sep 24, 2015
    KickBack likes this.
  8. KickBack

    KickBack

    Joined:
    Nov 18, 2014
    Posts:
    10
    I believe it was me who necroed the thread. Oops!

    (I saw Aug 26 and thought, ok this is recent enough!)
     
    Pecek and Gigiwoo like this.
  9. blendNZjnr

    blendNZjnr

    Joined:
    Nov 25, 2013
    Posts:
    7
    Hi @Gigiwoo & @KickBack & @StarManta

    I've been searching for answers everyone and this thread is relevant enough for me to ask:

    1. When to use localPosition (as this unity3d.com tutorial does in his drag script) vs anchoredPosition vs offsetMax and offsetMin (as I've seen recommended elsewhere and am currently using myself) ???

    2. Is it more efficient to script these transitions (using Lerp) or to use the animator?

    3. Can you even control the rotation of a rectTransform using script? I've tried and searched and can't find a non read-only rect.* or rectTransform.* paramater for rotation

    Thanks in advance for your help! I'm freshly learning C# for Unity, I've come from a 3D design background in blender and now jumping into Unity
     
  10. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,744
    Truth is, a lot of the time it won't make a difference. If you're working with RectTransform there's little reason to use localPosition. offsetMax/offsetMin versus anchoredPosition only really matters if your RectTransform has split anchors, and you need to be really specific about how it handles them.

    Probably can be slightly more efficient if scripting, but it'd be a tiny difference if any.

    Er.... rectTransform.rotation exists, and works exactly like transform.rotation...
     
    Gigiwoo likes this.
  11. Gigiwoo

    Gigiwoo

    Joined:
    Mar 16, 2011
    Posts:
    2,981
    Also, I got an email from tech support that the performance of the UI was massively improved in 5.2. I have not personally tested it.

    Gigi
     
  12. blendNZjnr

    blendNZjnr

    Joined:
    Nov 25, 2013
    Posts:
    7
    Hey thanks so much for the quick replies!

    Hmm I'll illustrate my problem:

    Code (CSharp):
    1. GetComponent<RectTransform> ().offsetMax = Vector2.Lerp (oldPositionMax, newPositionMax, percentage);
    2. GetComponent<RectTransform> ().offsetMin = Vector2.Lerp (oldPositionMin, newPositionMin, percentage);
    3. GetComponent<RectTransform> ().rotation.eulerAngles.z =  Mathf.Lerp (0f, 90f, percentage);
    The top two lines work perfectly fine... the bottom line gets "Cannot modify a value type return value of `UnityEngine.Transform.rotation'. Consider storing the value in a temporary variable"

    I get the same error with every alternative reference I can think of...
    Code (CSharp):
    1. gameObject.transform.localRotation.eulerAngles.z = Mathf.Lerp (0f, 90f, percentage);
    Code (CSharp):
    1. GetComponent<RectTransform> ().localRotation.eulerAngles = Vector3.Lerp (new Vector3(0f,0f,0f), new Vector3(0f,0f,90f), percentage);
    Would love to understand what this error is actually telling me... how does Unity decide what can and can't be modified?

    [edit] I also note after posting those codes here that the unmodifyable pieces of code are showing up purple whereas the modifyable properties are showing up blue (this doesn't happen in my MonoDevelop) - is this a clue???
     
  13. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,744
    When you access transform.rotation (or transform.position; it works the same way), the engine actually generates a new Quaternion (or Vector3) for you to use. You can assign a new value to transform.position or transform.rotation, but you can't change them directly, because what you're changing is basically a copy. The reason your offsetMin/offsetMax assignments work is because you're assigning new numbers to them wholesale. (TBH, I'm not entirely certain whether you can modify offsetMin and offsetMax directly; they might not be the same as transform.position and .rotation)

    Or, put another way, transform.rotation is a property, which is to say it's a pair of functions for accessing and assigning a Quaternion that's designed to look like a member variable. You could accurately think of these two statements as being equivalent:
    Code (csharp):
    1. transform.rotation.eulerAngles.z = 90f;
    2. transform.GetRotation().eulerAngles.z = 90f;
    GetRotation doesn't exist, but you get the idea - .rotation is not a variable.

    A second issue you will probably run into is that eulerAngles is rarely a reliable way to play with Quaternions, because Euler angles are something of a second language to Quaternions. You especially shouldn't set one component of the Euler angle without setting the other two at the same time, because it's not reliable that the other two will be the same as you expect.

    Let me give you an example. Create two GameObjects. Set the rotation of one of them to 90,0,0 and the other one to 90,90,90. Now look back and forth between them. Notice that they're both the same? Now, imagine you're a quaternion, and you don't know what three numbers I typed into the inspector, but you do know that this orientation is what you represent. When I ask you for your Euler angles, both (90,90,90) and (90,0,0) are perfectly correct ways of representing that orientation. When you call transform.rotation.eulerAngles, that's exactly what you're asking it for. Sometimes it'll give you 90,0,0, and I seem to get that most of the time; maybe the Quaternion class's algorithm prefers that. But if it returns 90,90,90, it's still 100% correct.

    So now, let's do what you're trying to do there, and change one component. Let's change the x from 90 to 45. Notice that 45,90,90 and 45,0,0 are not even remotely the same orientation, not like 90,90,90 and 90,0,0.

    So A) don't alter transform.rotation, replace it; and B) if you're not going to set all three, don't use eulerAngles at all. Also, in terms of efficiency, modifying Euler angles is effectively calling Quaternion.AngleAxis three times. In summary, what you really want to be doing is probably this:
    Code (csharp):
    1. transform.rotation = Quaternion.AngleAxis(Mathf.Lerp(0f, 90f, percentage), Vector3.forward);
     
    AShenawy likes this.
  14. blendNZjnr

    blendNZjnr

    Joined:
    Nov 25, 2013
    Posts:
    7
    Ok! Thank you @StarManta, at least I now have code that works.

    Does it matter that you're using transform.rotation rather than rectTransform?
    (ie is it different than GetComponent<RectTransform> ().rotation = )?

    Thank you for trying to explain the difference between changing a value and replacing it...
    still a bit confused...

    how is:
    transform.rotation =
    considered as 'replacing'

    but:
    transform.rotation.eulerAngles.z =
    considered as trying to 'change' ???

    (acknowledging your correctness that changing single eulerAngle values would be a bad idea and that I nolonger want to do this)

    Or is it all to do with what comes after the =...
     
  15. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,744
    Nope. The RectTransform is a Transform; any property that's inherited from Transform, you can use from either and it'll behave the same. You only really need to use the RectTransform if you need to access its properties that aren't shared with Transform.

    Let's explain this by creating our own member variable that behaves the same way. I'm gonna use Vector3's mostly because I'm used to explaining this concept with transform.position and I keep typing Vector3 anyway. :p

    Code (csharp):
    1. public class YourThing : MonoBehaviour {
    2. public Vector3 someVector = Vector3.zero;
    3. }
    4. // elsewhere (exhibit A)
    5. YourThing someThing = GetComponent<YourThing>();
    6. someThing.someVector.x = 0f;
    This works. This is probably how you're thinking transform.position and transform.rotation works, because someThing.someVector.x looks just like transform.position.x. However, despite the similar appearance, they're not the same. I'll get to that in a minute.

    Now, let's say you want to add some restrictions to the Vector3. You want to clamp its X position within a certain range, for example. You could make the variable private and add some functions to get its value and alter it:
    Code (csharp):
    1.  
    2. public class YourThing : MonoBehaviour {
    3. private Vector3 someVector = Vector3.zero;
    4. public void SetVector(Vector3 newVector) {
    5. someVector = newVector;
    6. someVector.x = Mathf.Clamp(someVector.x, 0f, 10f);
    7. }
    8. public Vector3 GetVector() {
    9. return someVector;
    10. }
    11. }
    In computer science jargon, this is called encapsulation. You're protecting the real value from being modified by other classes so that your class can rely that its value is treated a certain way. So now, you can hopefully see why this doesn't work:
    Code (csharp):
    1. //exhibit B
    2. someThing.GetVector().x = 0f;
    In this case, I think this will not throw an error, but it will definitely not change someVector's value. to do that, you have to do this:
    Code (csharp):
    1. Vector3 tempVector = someThing.GetVector();
    2. tempVector.x = 0f;
    3. someThing.SetVector(tempVector);
    Now, writing GetVector and SetVector all the time gets annoying. So you can put these things into what's called a property.
    Code (csharp):
    1.  
    2. public class YourThing : MonoBehaviour {
    3. private Vector3 secretSomeVector = Vector3.zero;
    4. public Vector3 someVector {
    5. get {
    6. return secretSomeVector;
    7. }
    8. set {
    9. secretSomeVector = value;
    10. secretSomeVector.x = Mathf.Clamp(secretSomeVector.x, 0f, 10f);
    11. }
    12. }
    13. }
    14. //elsewhere
    15. Vector3 tempVector = someThing.someVector;
    16. tempVector.x = 0f;
    17. someThing.someVector = tempVector;
    18. //but not...
    19. someThing.someVector.x = 5f;
    It's important to realize that, even though this last line looks identical to Exhibit A, when it's compiled, it actually looks identical to Exhibit B. A property is a pair of functions that masquerade as a variable because it's convenient. You get the benefits of encapsulation without needing to write SetVector and GetVector all the time. (It's not always a pair; sometimes it's only a getter function. This is usually the case for 'read only' variables.)

    transform.position is a property because that value doesn't really exist. It's really stored as a local position (accessible by transform.localPosition, though I think that's a property too), and when you access transform.position, it goes up the hierarchy and applies the positions, rotations, and scales of every parent transform. When you assign something to transform.position, it does the inverse, and assigns the modified value to its local position variable. (Efficiency note: because of all this math, transform.localPosition is always a better choice than transform.position unless you actually need its world space position. Some for .rotation and .localRotation.)

    .....anyway, like I said, transform.position is a property, which means that it's really a getter and setter function that looks like a variable.
     
  16. blendNZjnr

    blendNZjnr

    Joined:
    Nov 25, 2013
    Posts:
    7
    Wow, thank you for taking the time to explain that so thoroughly!

    I definitely didn't know any of that previously, and now it's starting to make sense :)
     
  17. moh05

    moh05

    Joined:
    Nov 26, 2015
    Posts:
    65
    Hello guys,

    Anyone knows how to move a Rectransform between two Normal Transforms ?

    Thanks in advance
     
  18. Olly

    Olly

    Joined:
    Jun 19, 2014
    Posts:
    20
    For anyone else stuck on these kind of things; simply use the animator; it tells you the properties that it changes.
     
    ericbegue, rmon222, BitGamey and 2 others like this.
  19. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    That's one of those ideas that is so simple as to be genius!!!
     
    Olly likes this.
  20. ColinStuart

    ColinStuart

    Joined:
    May 5, 2017
    Posts:
    1
    3 hours of hair pulling and searching for a simple solution and if it wasn't there the whole time. Thank you so much.
     
  21. AcademyOfFetishes

    AcademyOfFetishes

    Joined:
    Nov 16, 2018
    Posts:
    219
    Can you elaborate on this point? In what way does it matter when you have split anchors? I don't even understand what offets and anchoredPositions represent conceptually. It's really hard to find the answer to this question.
     
  22. kenan238

    kenan238

    Joined:
    Mar 7, 2020
    Posts:
    3
    no i need to move it quick
     
  23. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,744
    You misunderstood. He's not saying to use the animator to set the values in your final code (though you could, and it's not automatically slow, but nevermind that). Rather, create an animation where you move the RectTransform in the way that you want, then look at the animation window and see which values on the RectTransform it animated. Those are the values you want to change in code.
     
    AShenawy likes this.
  24. AShenawy

    AShenawy

    Joined:
    Apr 3, 2018
    Posts:
    4
    @StarManta I just read your earlier posts explaining the property thing, why it's better to use local position in most cases directly related to the object and just wanted to thank you for the detailed explanation.
    It's rare to have somebody explain things thoroughly and not in a 2-3 line sentence which just create even more questions!
     
  25. baoquoc860

    baoquoc860

    Joined:
    May 19, 2019
    Posts:
    3
    Thank you so much :)
     
  26. Brother_77

    Brother_77

    Joined:
    Feb 8, 2019
    Posts:
    226
    RectTranform.anchoredPosition is used to move over time/frames to a desired RectTransform.anchored position Vector2.

    Create an empty object on the canvas and use it as target:
    Code (CSharp):
    1. public RectTransform targetPos;
    2. public RectTransform objectToMove;
    3.  
    4. StartCoroutine(MoveRectTransformToTarget(targetPos.anchoredPosition, 300.0f));
    5.  
    6. private IEnumerator MoveRectTransformToTarget(Vector2 posTarget, float speed)
    7.  
    8.  {
    9.      while (objectToMove.anchoredPosition != posTarget)
    10.      {
    11.       objectToMove.anchoredPosition = Vector2.MoveTowards(objectToMove.transform.position,          posTarget, speed * Time.deltaTime);
    12.       yield return null;
    13.       }
    14.  
    15.       Debug.Log("TargetReached " + objectToMove + "Target: " + posTarget);
    16.       if (Vector2.Distance(objectToMove.anchoredPosition, posTarget) < 1.0f)
    17.       {
    18.         Debug.Log("Distance " + objectToMove + "Target: " + posTarget);
    19.       }
    20.  
    21. }
     
    Last edited: Oct 22, 2021