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

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?

2 Likes

I guess you wanna reposition your UI. Do it like that:

using UnityEngine.UI;

void Start()
{
GetComponent().localPosition = your position;
}

This should be it.

5 Likes

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.

20 Likes

Glad to see others with similar thoughts. Wonder what the devs have in mind…
Gigi

1 Like

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?

8 Likes

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?)

@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

1 Like

I believe it was me who necroed the thread. Oops!

(I saw Aug 26 and thought, ok this is recent enough!)

2 Likes

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

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…

1 Like

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

Hey thanks so much for the quick replies!

Hmm I’ll illustrate my problem:

GetComponent<RectTransform> ().offsetMax = Vector2.Lerp (oldPositionMax, newPositionMax, percentage);
GetComponent<RectTransform> ().offsetMin = Vector2.Lerp (oldPositionMin, newPositionMin, percentage);
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…

gameObject.transform.localRotation.eulerAngles.z = Mathf.Lerp (0f, 90f, percentage);
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???

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:

transform.rotation.eulerAngles.z = 90f;
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:

transform.rotation = Quaternion.AngleAxis(Mathf.Lerp(0f, 90f, percentage), Vector3.forward);
1 Like

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 ().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 =…

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. :stuck_out_tongue:

public class YourThing : MonoBehaviour {
public Vector3 someVector = Vector3.zero;
}
// elsewhere (exhibit A)
YourThing someThing = GetComponent<YourThing>();
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:

public class YourThing : MonoBehaviour {
private Vector3 someVector = Vector3.zero;
public void SetVector(Vector3 newVector) {
someVector = newVector;
someVector.x = Mathf.Clamp(someVector.x, 0f, 10f);
}
public Vector3 GetVector() {
return someVector;
}
}

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:

//exhibit B
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:

Vector3 tempVector = someThing.GetVector();
tempVector.x = 0f;
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.

public class YourThing : MonoBehaviour {
private Vector3 secretSomeVector = Vector3.zero;
public Vector3 someVector {
get {
return secretSomeVector;
}
set {
secretSomeVector = value;
secretSomeVector.x = Mathf.Clamp(secretSomeVector.x, 0f, 10f);
}
}
}
//elsewhere
Vector3 tempVector = someThing.someVector;
tempVector.x = 0f;
someThing.someVector = tempVector;
//but not...
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.

4 Likes

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 :slight_smile:

Hello guys,

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

Thanks in advance

For anyone else stuck on these kind of things; simply use the animator; it tells you the properties that it changes.

5 Likes

That’s one of those ideas that is so simple as to be genius!!!

1 Like

3 hours of hair pulling and searching for a simple solution and if it wasn’t there the whole time. Thank you so much.

2 Likes