Is there a prefered way to get the position and size of a RectTransform in Screen Coordinates, suitable for using in a script to compare with mouse clicks?
This is an issue that I have struggled with a lot, trying to use Unitys UI system. There seems to be dozens of different solutions on the net, may of which seems overly complex for such a simple thing.
I’ve tried converting to and from world space, which seems to work for Screen Space - Camera, but not in overlay mode, Using RectTransformUtility.
I’ve tried using RectTransform.rect and called Canvas.ForceUpdateCanvases in the Start method, no luck either.
I use transform.position for bottom left coordinates and transform.rect.height transform.rect.width to calculate the top right coordinates. My particular issue was that the pivot point of the y-axis was set to 1 and needed to be accounted for.
I have no idea if this will work in all circumstances or if other variables than pivot point could affect this. I keep getting in trouble regularly doing these kind of calculations.
Option A))
Maths, RectTransform.anchorMax & anchorMin, and screen.width & height. With these vars you can get location and size parameters.
Note that they are normalized which means they exist between 0->1 or we can see them as percentage across the vector where 1 = full width or height.
float rectWidth = (yourRect.anchorMax.x - yourRect.anchorMin.x)*Screen.width;
float rectHeight =(yourRect.anchorMax.y - yourRect.anchorMin.y)*Screen.height;
Vector2 position = new Vector2(yourRect.anchorMin.x*Screen.width, yourRect.anchorMin.y * Screen.height);
//world pos, width and height, all points found thusly
//create a new rect or redefine yourRect then we'll use
Touch touch = Input.GetTouch(0);
if(yourRect.Contains(touch.position))
{
//do stuff if touched
}
Option B))
There’s a few steps, but it’s quite easy and we only need three provided class methods and a loop or two.
1.void RectTransform.GetWorldCorners(Vector3[ ] v);
//where v is the Vector3 array to return the corners to in world coordinates.
2.Vector3 Camera.main.WorldToScreenPoint(Vector3 p);
//where p is the Vector3 point representing a corner
and after using the two above to either create an abstract test region or refactor the ret we have to screen space coords, we check for touch using the same method as before.
3.bool Rect.Contains(Vector3 testPoint)
TO Future people, feel free to reach out if you need more help.
Thx, but also not perfect.
If one want to get the size in screen pixels you have to mlutiply the scale factor of the Canvas (if it is set to scale with screen size)
public static class RectTransformExtension
{
public static Canvas GetCanvas(this RectTransform rt)
{
return rt.gameObject.GetComponentInParent<Canvas>();
}
public static float GetWidth(this RectTransform rt)
{
var w = (rt.anchorMax.x - rt.anchorMin.x) * Screen.width + rt.sizeDelta.x * rt.GetCanvas().scaleFactor;
return w;
}
public static float GetHeight(this RectTransform rt)
{
var h = (rt.anchorMax.y - rt.anchorMin.y) * Screen.height + rt.sizeDelta.y * rt.GetCanvas().scaleFactor;
return h;
}
}
All the “recent” answers did not work in my case. I believe this only works if you have a specific anchor. I have a stretched anchor on x and y, so the width and height is exactly the same as the screen resolution.
yes, as of Unity 2020, you can use the RectTransformUtility.ScreenPointToLocalPointInRectangle method ( docs ). It’s what the Slider class uses, so it’s pretty safe. (for the camera parameter, you can pass in eventData.pressEventCamera, if you are using a PointerEventData, or leave it as null if you’re sure your canvas is in screen overlay mode)
note that this is the opposite method of what the OP wanted, but the result is the same, which is checking if a screenspace point hits our RectTransform.
Absolute lifesaver, this eventData.pressEventCamera was what I was missing!
Turns out using Camera.main doesn’t always use camera you think it’s using, I have a custom slider implementation which worked in one scene, but not in another (settings screen used in main menu and in game), and the cameras are different in each. This fixed it though thank you!
My usecase was, besides the height and width, to actually get the screen coordinates of the corners of the rect and this was the solution that worked. As to why height needs to be added twice, no idea.
public enum Corner { TopLeft, TopRight, BottomLeft, BottomRight }
static public float RectGetX(RectTransform rect, float canvas_scale_factor) => (rect.anchorMin.x) * Screen.width + rect.rect.x * canvas_scale_factor;
static public float RectGetY(RectTransform rect, float canvas_scale_factor) => (rect.anchorMin.y) * Screen.height + rect.rect.y * canvas_scale_factor;
static public float RectGetWidth(RectTransform rect, float canvas_scale_factor) => (rect.anchorMax.x - rect.anchorMin.x) * Screen.width + rect.sizeDelta.x * canvas_scale_factor;
static public float RectGetHeight(RectTransform rect, float canvas_scale_factor) => (rect.anchorMax.y - rect.anchorMin.y) * Screen.height + rect.sizeDelta.y * canvas_scale_factor;
static public Vector2 RectGetCorner(RectTransform rect, float canvas_scale_factor, Corner corner)
{
switch (corner)
{
case Corner.BottomLeft:
return new Vector2(RectGetX(rect, canvas_scale_factor), RectGetY(rect, canvas_scale_factor) + RectGetHeight(rect, canvas_scale_factor));
case Corner.BottomRight:
return new Vector2(RectGetX(rect, canvas_scale_factor) + RectGetWidth(rect, canvas_scale_factor), RectGetY(rect, canvas_scale_factor) + RectGetHeight(rect, canvas_scale_factor));
case Corner.TopLeft:
return new Vector2(RectGetX(rect, canvas_scale_factor), RectGetY(rect, canvas_scale_factor) + 2 * RectGetHeight(rect, canvas_scale_factor));
case Corner.TopRight:
return new Vector2(RectGetX(rect, canvas_scale_factor) + RectGetWidth(rect, canvas_scale_factor), RectGetY(rect, canvas_scale_factor) + 2 * RectGetHeight(rect, canvas_scale_factor));
default:
throw new System.Exception("Unknown corner: " + corner);
}
}
The canvas_scale_factor you can get via scaleFactor on the cavas (wanted to avoid calling GetComponent in each call).
EDIT: The confusing need of height is because of the pivot I’m using.
The code above works for a pivot of [0,1]!
always remember that you can have a Canvas field and either set that in the inspector or set that once in awake with get component. After that you can just reference the field and not find it every call. But maybe that’s what you did haha