- Home /
How to get width of RectTransform? (in screen coordinates, for position relative to, say, mouse input)
Here is what I have:
Unity 4.6 (Beta 20)
Scene with Panel UI object.
Panel's anchors are set to its four corners.
Panel's pivot point is the center (0.5, 0.5).
Wrote code to position the panel relative to the mouse as the mouse moves around.
Here is the code that is setting the position of the panel:
public GameObject goToolTipPanel; // Set in Unity IDE.
// Update is called once per frame
void Update()
{
if (blToolTipOn)
{
RectTransform rt = goToolTipPanel.GetComponent<RectTransform>();
rt.position
= new Vector3(
Input.mousePosition.x - rt.rect.width/2, // <--- WIDTH IS STRANGE HERE?!
Input.mousePosition.y,
goToolTipPanel.transform.position.z);
}
}
I expected the above code to show the panel entirely to the left of the mouse cursor (with the right edge of the panel at the mouse cursor's position). For example:
This isn't the case. Instead, the panel is significantly to the left of the mouse cursor (such that there is a large gap between the right edge of the panel and the cursor). For example:
In the Unity IDE, the panel's width is shown in the rect transform to be "198". A print statement in the code also shows that rt.rect.width is "198"; however moving the panel's position to the left by 198/2 does not do what I expected ... it moves it too far. I seem to need to move it by around "60" to actually move the panel as expected.
Can someone please explain what I'm doing wrong? Why is moving the position to the left by half of the size of the rect transform NOT moving the panel half its size to the left in screen coordinates?
How can I determine the actual screen coordinate width of the panel and re-position it relative to the mouse cursor?
Hi, I just used your code and it works as expected here.
Are you using Screen Space - Overlay mode?..
That is really strange that it worked for you. There must be some difference with how I have things configured, but I'm not sure what ...
I double checked my canvas and confirmed it is set to Screen Space - Overlay mode.
I'll try to test things again in a brand new project and see if that makes any difference.
Ok, so I have some more data.
I tried again in a fresh project and found that things worked. I also figured out what it was about my old project that was causing the issue.
$$anonymous$$y base Canvas has a Resolution Reference component to properly scale the text to different resolutions. When this component is enabled, the problem I described above happens. When this component is disabled, everything works great! (?)
So, can someone please explain why the Resolution Reference is having this impact and how to get around it? I think I need the component for text to work properly in the UI ... so I'm kind of stuck at the moment about what to do.
Thanks.
Another update ...
Unsurprisingly, I suppose, the following tweak to the code fixed my original issue:
Input.mousePosition.x - (rt.rect.width * (currentResolutionWidth / referenceResolutionWidth))/2 ...
However, I'm still interested to find out more about what exactly the Resolution Reference component is doing. Documentation on it seems very slim on the web.
It is seriously ridiculous that this cant be done in a straightforward way when using anchors in the 4 corners of the parent. All i want is to find the distance between the anchors so that i can control the actual size of the contents using the offsets. But there is NO reliable way to do it. sizeDelta apparently returns the size difference to the parent when 4 corners mode is used. Why does this have to be so hard. Its not like the back end code doesnt know how far apart the anchors are or how large the parent is. Why cant you just expose a reliable means of checking it to the front end code...
Answer by troien · Oct 12, 2014 at 04:24 AM
Multiply the width of the RectTransform by the Canvas.scaleFactor to get the correct width you can use for the position.
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(RectTransform))]
public class TestScript : MonoBehaviour
{
public bool blToolTipOn;
public Camera camera;
public Canvas canvas;
// Update is called once per frame
void Update()
{
if (blToolTipOn)
{
RectTransform rt = GetComponent<RectTransform>();
Vector3 input = Input.mousePosition;
input.x -= ((rt.rect.width * canvas.scaleFactor) / 2); // The important part!
// In my case, I needed to do this aswell, you probable don't need this in your setup and can just set rt.position with the input instead
Vector3 output;
RectTransformUtility.ScreenPointToWorldPointInRectangle(rt, input, camera, out output);
rt.position = output;
}
}
}
EDIT: 30-NOV-2016
As per comments, this are two other ways to do it. I think better ways as they handle a lot of edge cases better. The resolution reference is now called canvas scaler, which is a much better name for it as it tells you what it does better. It scales the canvas ;) And because it scales the canvas, the thing you really have to do is take note that RectTransform.rect it's size is its local size, to get the world size, you've got to scale it properly. Which is what I do in the following code.
using UnityEngine;
public class Example : MonoBehaviour
{
public bool blToolTipOn;
public bool setpivot; // if true is sets the pivot, if false not
public Canvas canvas;
void Update()
{
if (blToolTipOn)
{
RectTransform rt = GetComponent<RectTransform>(); // The recttransform to drag
RectTransform plane = canvas.GetComponent<RectTransform>(); // On which plane we drag the object
Vector3 input = Input.mousePosition; // In screen space
Camera cam;
switch (canvas.renderMode)
{
case RenderMode.ScreenSpaceOverlay:
cam = null;
break;
case RenderMode.ScreenSpaceCamera:
case RenderMode.WorldSpace:
cam = canvas.worldCamera;
break;
default:
throw new System.NotImplementedException();
}
Vector3 output;
if (RectTransformUtility.ScreenPointToWorldPointInRectangle(plane, input, cam, out output))
{
if (setpivot)
{
rt.pivot = new Vector3(1f, 0.5f); // set the pivot to the right center before setting the position
// Example for left side
//rt.pivot = new Vector3(0f, 0.5f);
}
else
{
// example for right side
output += rt.TransformVector(-(rt.rect.width * (1f - rt.pivot.x)), 0, 0); // offset our position by the amount of pixels between the pivot and the right side
// Example for left side
//output += rt.TransformVector(rt.rect.width * rt.pivot.x, 0, 0);
}
rt.position = output;
}
}
}
}
Yep, that's the magic!
Thanks a bunch troien. Very helpful!
This is btw indeed caused by the ReferenceResolution Component. This is because when you use this component there are basically 3 resolutions you need to think about.
1: The resolution of the window (Screen.width and Screen.height) 2: The Resolution of the ReferenceResolutionComponent (the value you specify in the inspector) 3: The resolution of the canvas. ($$anonymous$$ost often either the height or width will match that of te value given in the ReferenceResolution component)
If the width and height of the window matches that of the ReferenceResolution then all 3 values will be that of the ReferenceResolution component and the thing you tried to do initially might just work ;).
Input.mouseposition is based on the resolution of the window.
Width and height of the RectTransform is based upon the resolution of the canvas if i'm correct.
RectTransform.position is the actual world position in the scene. (That is why I needed to use RectTransformUtility.ScreenPointToWorldPointInRectangle
Thanks, this is great info, it already brought me ahead. But what about when you have a Canvas with "Screen Space - Camera" render mode? It seems that the position is different then. At least, it doesn't work for me.
The position should still be the same, did you assign the correct camera in the inspector of this script?
This is a bit of an old post however. And although the above script I posted does still work for me when I change to "screen space - camera". I think the following code is better/more robust as I just tested it with all 3 render modes without any problems and I think it is also easier to modify to your own needs
The important part is that you know in which coordinate space you are modifying your values. So is the rect you get from rt.rect in the local space of rt, Input.$$anonymous$$ousePosition is in screen space.
In the thing I post now I do this in local space ins$$anonymous$$d of screen space. The main difference is that it is much easier to convert everything to world space compared to converting it to screen space first.
First I get the world position of my click relative to the canvas (think of it as the mouse raycasts from the camera into the world, and then get the world position where it intersects the canvas)
Then I use RectTransform.TransformVector to convert my offset of half the width from local space (RectTransform.rect is in local space) to world space and then add that offset to our output.
public class Example : $$anonymous$$onoBehaviour
{
public bool blToolTipOn;
public Canvas canvas;
void Update()
{
if (blToolTipOn)
{
RectTransform rt = GetComponent<RectTransform>(); // The recttransform to drag
RectTransform plane = canvas.GetComponent<RectTransform>(); // On which plane we drag the object
Vector3 input = Input.mousePosition; // In screen space
Camera cam;
switch (canvas.render$$anonymous$$ode)
{
case Render$$anonymous$$ode.ScreenSpaceOverlay:
cam = null;
break;
case Render$$anonymous$$ode.ScreenSpaceCamera:
case Render$$anonymous$$ode.WorldSpace:
cam = canvas.worldCamera;
break;
default:
throw new System.NotImplementedException();
}
Vector3 output;
if (RectTransformUtility.ScreenPointToWorldPointInRectangle(plane, input, cam, out output))
{
// If you can change the pivot, this is the easiest way
#region Example1 ChangePivot
//rt.pivot = new Vector3(1f, 0.5f); // set the pivot to the right center before setting the position
//rt.position = output;
#endregion
// Otherwise, in my tests this seems to work better then what I posted in 2014.
#region Example2 Offset world position
output += rt.TransformVector(-(rt.rect.width / 2f), 0, 0); // add offset (move half of our width to the left)
rt.position = output;
#endregion
}
}
}
}
Thanks troien for the response! This is a great post, will surely come back to it when I need to do this again. In the end the information you gave me (making sure what space I'm using, and what is the camera space) and going through the code again helped solve the situation.
Your answer
Follow this Question
Related Questions
How to convert from world space to canvas space? 15 Answers
Rect to RectTransform on overlay Canvas? 1 Answer
Shortcut to set anchors at corners? 3 Answers
RectTransform position won't zero 0 Answers