- Home /
Rotating and Scaling "Stretched" UI Elements
First, as a preface, in order to enforce a minimum resolution on either axis for the UI Canvas, I'm currently taking an arbitrary dimension (let's say it's 1024), and performing the following transformation:
CanvasScaler scaler = GetComponent<CanvasScaler>();
float referenceSize = 1024.0f;
scaler.uiScaleMode = CanvasScaler.ScaleMode.ConstantPixelSize;
float wRatio = Screen.width / referenceSize;
float hRatio = Screen.height / referenceSize;
scaler.scaleFactor = Mathf.Min(wRatio, hRatio);
As can be see from my example, I'm ensuring that the Canvas is based around a Constant Pixel Size, then scaled (scaleFactor) so that the shorter dimension is 1024 pixels as its basis.
For the UI elements themselves ( RectTransform), I'm performing rotations by changing the position of the UI element ( localPosition and/or anchoredPosition), then simply rotating the UI element around its pivot point. In this regard, everything works just as intended.
However, I've run into a problem in attempting to properly honor alignment for a "stretched" RectTransform.
If a UI element is stretched horizontally all the way across the screen (at any resolution), it would have X anchors ( RT.anchorMin.x and RT.anchorMax.x) at 0 and 1 respectively, with Left and Right offsets of 0 (applied by RT.offsetMin.x and RT.offsetMax.x or RT.sizeDelta to combine them).
When such a UI Element is rotated 90 degrees, it will extend vertically by the same number of pixels naturally. In this regard, it would extend beyond the top and bottom of the screen on a Landscape screen aspect ratio or would take up only a portion of the height with a Portrait aspect ratio.
Therefore, in order to make up for the difference, each side needs to be drawn in based on the difference in pixel count extending toward each end of the screen. For example, if the Canvas size is 1024x2048 or 2048x1024, I have a +/- 1024-pixel difference to make up for upon rotating the UI element 90 degrees. By adjusting the offsets/ sizeDelta accordingly, this *SHOULD* result in the UI element stretching to fit the new " width" it's aligned to.
However, this has not been the case. Rather, much more problematically, this *HAS* been the case in a Landscape aspect ratio, whereas for Portrait, I must multiply that difference by the canvas height/width in order to properly fit.
This should be especially straightforward and simple ( width - height = the difference between them for sizeDelta to compensate), so I've been baffled, unable to figure out why there would be a difference in this calculation depending on whether the aspect ratio is Portrait or Landscape.
// Canvas Rect Transform
RectTransform crt = transform.parent as RectTransform;
// negative to expand, positive to contract
float sizeDeltaRotated = crt.rect.height - crt.rect.width;
// When rotated 90 degrees...
// -- Landscape
thisRectTransform.sizeDelta = sizeDeltaRotated;
// -- Portrait
float hwRatio = crt.rect.height / crt.rect.width;
thisRectTransform.sizeDelta = hwRatio * sizeDeltaRotated;
Why has this difference in calculation been necessary for me to get equivalent results? Has there been a behavior I've overlooked when defining the ScaleFactor in this manner, or have I been missing something else entirely?
Answer by Eno-Khaon · Apr 17, 2018 at 06:26 PM
Okay, I found the root of the problem.
Changing the Canvas' uiScaleMode doesn't take effect immediately. The changes will have propagated after one frame has passed, but that's no good...
Looks like I'll probably just need to compute the sizes myself instead of relying on the Canvas' height and width.
For reference, the reason I was seeing inconsistent scaling behavior is because I'd initially set up the UI using the Canvas Scale Mode " ScaleWithScreenSize". By using the script (mentioned at the start of my question) to resize the UI Elements, it was already successfully matching my current Scaling settings in the case of Landscape orientation ( matchWidthOrHeight value of 1 meant Unity was automatically accommodating Landscape aspect ratio ahead of time in this situation).
So, in the end, my solution is to simply calculate the new Canvas resolution, set those variables aside, and read from them without having to wait until the next frame for the Canvas to fully update.
// Script on Canvas (Excerpt)
private float fWidth;
public float width
{
get
{
return fWidth;
}
}
private float fHeight;
public float height
{
get
{
return fHeight;
}
}
void OnEnable()
{
float wRatio = Screen.width / referenceSize;
float hRatio = Screen.height / referenceSize;
float minScale = Mathf.Min(wRatio, hRatio);
fHeight = Screen.height / minScale;
fWidth = Screen.width / minScale;
}
// Script on UI element (Excerpt, unpolished)
CanvasResize cr = transform.parent.GetComponent<CanvasResize>();
sizeDeltaRotated = (cr.height - cr.width) + thisRectTransform.sizeDelta;
Yep, the solution was fundamentally the same as what I was already doing. I just hadn't realized that the Canvas wasn't updating in full immediately, so I was being given inaccurate data to work with from its RectTransform.
If anyone has any input regarding a means of updating the Canvas' RectTransform more immediately, I encourage you to bring it up; the more and more thorough information available here, the better. However, for all intents and purposes, this problem is solved.
Found a handy piece of information I didn't consider looking up until knowing that the Canvas updates at the end of a frame:
https://docs.unity3d.com/ScriptReference/Canvas.ForceUpdateCanvases.html
Edit: Scratch that. Once I tried it, that still didn't actually update the RectTransform upon use.