I have a screen space canvas and I simply want to move a “target” over an object in world space. My world space coordinate is obtained with a raycast and I do a debug draw to make sure it is correct. The problem is none of the following code is giving me the right result.
void Update ()
{
if( currentSelection==null) return;
// Translate world position to UI coordinates
RectTransform rt = (RectTransform) this.transform;
Vector3 pos = ( (RectTransform)rt.parent ).InverseTransformPoint( currentSelection.position );
rt.position = pos; // nether rt or rt.parent works!
}
//this is your object that you want to have the UI element hovering over
GameObject WorldObject;
//this is the ui element
RectTransform UI_Element;
//first you need the RectTransform component of your canvas
RectTransform CanvasRect=Canvas.GetComponent<RectTransform>();
//then you calculate the position of the UI element
//0,0 for the canvas is at the center of the screen, whereas WorldToViewPortPoint treats the lower left corner as 0,0. Because of this, you need to subtract the height / width of the canvas * 0.5 to get the correct position.
Vector2 ViewportPosition=Cam.WorldToViewportPoint(WorldObject.transform.position);
Vector2 WorldObject_ScreenPosition=new Vector2(
((ViewportPosition.x*CanvasRect.sizeDelta.x)-(CanvasRect.sizeDelta.x*0.5f)),
((ViewportPosition.y*CanvasRect.sizeDelta.y)-(CanvasRect.sizeDelta.y*0.5f)));
//now you can set the position of the ui element
UI_Element.anchoredPosition=WorldObject_ScreenPosition;
Vector2 pos = gameObject.transform.position; // get the game object position
Vector2 viewportPoint = Camera.main.WorldToViewportPoint(pos); //convert game object position to VievportPoint
// set MIN and MAX Anchor values(positions) to the same position (ViewportPoint)
rectTransform.anchorMin = viewportPoint;
rectTransform.anchorMax = viewportPoint;
I extended Sylos’s slightly by making it an extension method to UnityEngine.Canvas itself, giving the user the option to use a non-main camera if they’d like:
public static class CanvasExtensions
{
public static Vector2 WorldToCanvas(this Canvas canvas,
Vector3 world_position,
Camera camera = null)
{
if (camera == null)
{
camera = Camera.main;
}
var viewport_position = camera.WorldToViewportPoint(world_position);
var canvas_rect = canvas.GetComponent<RectTransform>();
return new Vector2((viewport_position.x * canvas_rect.sizeDelta.x) - (canvas_rect.sizeDelta.x * 0.5f),
(viewport_position.y * canvas_rect.sizeDelta.y) - (canvas_rect.sizeDelta.y * 0.5f));
}
}
It can be called like so:
// _ui_canvas being the Canvas, _world_point being a point in the world
var rect_transform = _ui_element.GetComponent<RectTransform>();
rect_transform.anchoredPosition = _ui_canvas.WorldToCanvas(_world_point);
You just have to include the namespace the CanvasExtensions class resides in
I wrote a static function which requires canvas rectransform, camera and vector position.
That way you can work with multiple canvas and cameras.
Works like a charm.
private Vector2 WorldToCanvasPosition(RectTransform canvas, Camera camera, Vector3 position) {
//Vector position (percentage from 0 to 1) considering camera size.
//For example (0,0) is lower left, middle is (0.5,0.5)
Vector2 temp = camera.WorldToViewportPoint(position);
//Calculate position considering our percentage, using our canvas size
//So if canvas size is (1100,500), and percentage is (0.5,0.5), current value will be (550,250)
temp.x *= canvas.sizeDelta.x;
temp.y *= canvas.sizeDelta.y;
//The result is ready, but, this result is correct if canvas recttransform pivot is 0,0 - left lower corner.
//But in reality its middle (0.5,0.5) by default, so we remove the amount considering cavnas rectransform pivot.
//We could multiply with constant 0.5, but we will actually read the value, so if custom rect transform is passed(with custom pivot) ,
//returned value will still be correct.
temp.x -= canvas.sizeDelta.x * canvas.pivot.x;
temp.y -= canvas.sizeDelta.y * canvas.pivot.y;
return temp;
}
Since this page is the second top result from my search, I will post my solution. This is probably something that didn’t work back in 2015, but fortunately Unity made it easier. This method eliminates the need to deal with confusing RectTransform math and properties.
GameObject healthBarGO; // a UI GameObject that is a child of the Canvas Transform
GameObject enemyGO; // for instance, maybe you want to draw a health bar over an enemy
Vector3 screenPosition = Camera.main.WorldToScreenPoint (enemyGO.transform.position); // pass the world position
healthBarGO.transform.position = screenPosition; // set the UI Transform's position as it will accordingly adjust the RectTransform values
This will draw the UI element exactly at the position of the enemy’s Transform, but you can offset the position before passing it to the WorldToScreenPoint method if you want the health bar to be drawn above or below the enemy’s Transform (or anywhere else, of course).
For others who might find this old post looking for the right way to do this, this works for all different canvas modes and anchors, and seems like it’s at least one of the ways Unity intended it to work:
I hadn’t the time to play around with the beta yet. So i can’t say much about the new UI system, but the canvas and the RectTransform positions are pure screenspace / canvas coordinates and have no relation to “worldspace”. Worldspace coordinates are actually mapped to screenspace / viewspace coordinates when rendered by a camera. If you want to know the screen / view space coordinate of a worldspace object, you have to use Camera.WorldToScreenPoint or Camera.WorldToViewportPoint of the camera that renders that object.
If you have the position in screenspace you might want to use the RectTransform hierarchy to get it as a relative position within a certain area.
I figured out that set position property of an new UI element on Awake() not have any effect. Probably because the layout system will set anchoredPosition on next frame, cleaning the position setted on Awake(). I figured out also that setting position property on Start() works nice.
Everyone seems to have added their two cents to this one, but somehow I still couldn’t get it working, so I’ll include my (hacky) solution below. This solution takes the Canvas’s scaleFactor into account, and it works with all anchors as long as anchorMin and anchorMax are the same.
public static Vector2 WorldToScreenWithScale(Canvas myCanvas, Camera camera, Vector2 targetPosition)
{
Vector2 screenPoint = RectTransformUtility.WorldToScreenPoint(camera, targetPosition);
screenPoint = screenPoint / myCanvas.scaleFactor; //apply scaling
return screenPoint;
}
public static void PlaceRectAtWorldPosition(Canvas myCanvas, Camera camera, Vector2 targetPosition, RectTransform myRect)
{
targetPosition = WorldToScreenWithScale(myCanvas, camera, targetPosition);
//get half of screen width and height, taking scaling into account
RectTransform canvasRect = myCanvas.GetComponent<RectTransform>();
int screenWidthFromCenter = (int)(Screen.width / myCanvas.scaleFactor) / 2;
int screenHeightFromCenter = (int)(Screen.height / myCanvas.scaleFactor) / 2;
//"convert" this value to bottom left of parent (i.e. pretend that anchor = 0,0)
Vector2 bottomLeftOfParent = new Vector2((-myRect.anchorMax.x * 2) * screenWidthFromCenter, (-myRect.anchorMax.y * 2) * screenHeightFromCenter);
//move the "converted" value to targetPosition
myRect.anchoredPosition = bottomLeftOfParent + targetPosition;
}
A little modification to the @YoungDeveloper version due to the fact that canvas could be nested and it’s RectTransform could be not a full-screen rect. And using a rect.size instead of sizeDelta, coz sizeDelta for a full-screen rect of the root canvas is usually (0, 0).
private Vector2 WorldToCanvasPosition(Canvas canvas, Camera worldCamera, Vector3 worldPosition) {
//Vector position (percentage from 0 to 1) considering camera size.
//For example (0,0) is lower left, middle is (0.5,0.5)
Vector2 viewportPoint = worldCamera.WorldToViewportPoint(worldPosition);
var rootCanvasTransform = (canvas.isRootCanvas ? canvas.transform : canvas.rootCanvas.transform) as RectTransform;
var rootCanvasSize = rootCanvasTransform!.rect.size;
//Calculate position considering our percentage, using our canvas size
//So if canvas size is (1100,500), and percentage is (0.5,0.5), current value will be (550,250)
var rootCoord = (viewportPoint - rootCanvasTransform.pivot) * rootCanvasSize;
if (canvas.isRootCanvas)
return rootCoord;
var rootToWorldPos = rootCanvasTransform.TransformPoint(rootCoord);
return canvas.transform.InverseTransformPoint(rootToWorldPos);
}
Here is my version using the built-in ScreenPointToLocalPointInRectangle method. The code assumes that you set your Canvas to Screen Space Overlay and that your target UI Element is anchored to the bottom left of the Canvas. If the latter is not the case you have to modify this part: 0.5f * CanvasRect.sizeDelta + …
Well I faced some problem when I trying to place a UI in front of some world space object, too. My original code works fun but when canvas scale it broke. After a while I realized that the scaleFactor in CanvasScaler is ALWAYS 1…
So I finally got my solution :
var sp = RectTransformUtility.WorldToScreenPoint(gameCamera, m_tracking*.transform.position);// gameCamera.WorldToViewportPoint();*
var rect = elementsRoot.rect;*
_ var cp = new Vector2(sp.x / Screen.width * rect.width,sp.y / Screen.height * rect.height);_
//cp would be the Vector2 that you want to place your UI element. elementsRoot in the code is simply a RectTransform
So I usually use my canvas’s Render Mode set as World Space and a child of the camera. That way I can have 3D objects as part of my UI.
Its probably no the most performant way [giggles]
-First I find the height that fits best for my canvas as it will be a constant in this case its 310
-then I add a boxCollider to my canvas
-then
public void SetCanvasSize(){
float w = Screen.width;
float h = Screen.height;
float ratio = h/w ;
if (ratio != 0) {
canvasrect.sizeDelta = new Vector2 (310 / ratio, 310);
boxCollider.size = new Vector3 (310 / ratio, 310, .1f);
}
}
-then I Raycast from my Camera position to the “World Object” I want and set the UI position to the hit point.
This seems to work well with different aspectRatio’s and stuff.