Controlling RectTransform at runtime

I know there are answers and threads about this already, most of them only have incomplete information and none mention all the things that can go wrong or explain what is going on in the background.

I am a professional UI builder in my day job, and although having a graphical editor for UIs can be useful, not being able to access and change any and all UI content from code at runtime is totally unacceptable.

I started building my own UI element prefabs in the hope of generating a modest library I could use across my projects, that allows simple UI elements to be added and positioned in the UI as required at runtime.

When accessing a RectTransform I can change its width, height, sizeDelta, anchorMin and -Max, localPosition, localScale, parenting and offsetMin and -Max and pivot. Seems great at first.

But when changing these values I noticed that the values that the transform had after the changes were not the same as the ones I input. Sometimes orders of magnitude different. It is clear that there is a calculation being done in a subsequent frame that recalculates all values based on the input values. I understand why this is the case, the transform needs to be dynamic based on resolution and aspect ratio, and I wish to take advantage of that feature and not switch it off. But this process is totally opaque, and as far as I can tell, not controllable.

It also appeared that the order in which the values were changed produced a different result. After trying countless different orders, and hours of frustrating trial and error, the only real step forward I have achieved is to understand that touching the pivot values in anyway is a big mistake and teleports the transform thousands of units into the middle of nowhere every time.

Is anyone able to shed some light on this situation? To narrow down the scope of answers I would love a description of how to achieve the following:

Given a canvas that fills the screen area, I would like to create a simple context menu. The steps would be

  • Created menu object in code
  • Load a prefab and parent it to the UI canvas
  • Position the menu transform/canvas in the correct position on the screen
  • Size the width of the element based on the widest content element
  • Size the height of the element based on the number of content elements
  • Generate all the content elements
  • Place them inside the menu element

What I am asking for here is instructions on how to achieve steps 3 to 5. Descriptions of why and how something works are also appreciated.

I doubt I am the only one who would like context menus in their game so this should hopefully be of general use to the community.

Thanks in advance

So not wanting to wait for replies that may never come, I have been experimenting. So far I have found out the following:

  • Always rescale to 1,1,1 after parenting as the transform is scaled relative to the parent during that step
  • Changing the deltaSize to a value less than 1 will have no effect. deltaSize 2,2 results in a transform twice as big as the anchor points rectangle. deltaSize 1,1 results in the same size, deltaSize 0.1,0.1 results in the same size as well.
  • The order of applying anchorMin and anchorMax with regard to deltaSize is not important
  • Elements are always centered, ignoring offsets. If you set the left offset (offsetMin.x) to 100 and the right offset (offsetMax.x) to 10, you will get a left offset of 55 and a right offset of 55
  • Setting anchorPosition changes the position of the drag widget in the editor, but does not change the position of the element
  • Setting localPosition has no effect at all

I am stuck here. I can resize my element as intended, but the position is always centered on the parent canvas. The offset values and the anchor position do not affect this at all.

So how can I set the position on the canvas?
Can anyone explain to me the observed results in items 2, 4, 5 and 6 to me?

Still no further progress on this, and no forum replies. Unity staff, please take this as a sign that no one is able to figure out how RectTransforms work and consider perhaps making the documentation a bit more detailed.

So I worked out one thing, the deltaSize is not a multiplier, so in a 400 by 400 canvas a 200 by 200 child will have deltaSize -200, -200

Ok I finally did it. No thanks to the documentation.

this.rectTransform.localScale = new Vector3(1, 1, 1);
this.rectTransform.anchorMax = new Vector2(1, 1);
this.rectTransform.anchorMin = new Vector2(0f, 0f);
this.rectTransform.sizeDelta = new Vector2(-(parentRect.width - newWidth), -(parentRect.height - newHeight));
this.rectTransform.offsetMin = new Vector3(newLeft, -newBottom);
this.rectTransform.offsetMax = new Vector3(-newRight, -newTop);

This is the code I ended up with.
This is called directly after setting the element’s parent to be the UI canvas
newWidth newHeight, newTop etc are in pixels and calculated separately.
All calculations need to be based on the size of the RectTransform of the parent element
Why the offsets are mostly negative is not clear.
this.rectTransform is the transform of the element to be positioned.

It is still not clear to me how it took me so many hours to figure this out, or why no one else on the forum was able to provide any information. It looks simple enough when it is done.

1 Like

I know it is an old post, but still the only one i found on the subject.

So I had the same problem, and it looks like what is usually enough is just to work on offsetMin and offsetMax. Those are the position of lower left corner, relative to the left default anchor, and the position of the upper right corner relative to the right default anchor.

Set the default anchor in prefab.

Then just modify these two items to set at the same time the position and the size.

An edit to my previous post: it works only if UI Scale Mode is “Constant Pixel Size”.
Otherwise Scale will be modified.
A workaround I’ve found:

        // Set position
        var rectTransform = item.GetComponent<RectTransform>();
        rectTransform.localScale = _contentRectTransform.localScale; // force scale to the same as parent RectTransform
        rectTransform.offsetMax = new Vector2(0, _nextItemYposition);
        rectTransform.offsetMin = new Vector2(0, _nextItemYposition - LineHeight);