Get real screen position for a canvas UI element

I’m “tweening” a sprite from a spot in my 2d world to a specific point in my overlay/HUD. When needed, I create an Image on my UI canvas and tween it to 0, 0, 0.

Problem is, I need to choose the correct screen position of another image, but can’t seem to find the correct values.

I have a canvas gameobject called HUDCanvas used to display my game’s HUD. It’s set to “Screen Space - Overlay” and defaults to a rect transform of x: 608.5, y: 371.

I have an image on this canvas, called “Hotbar”. It’s anchored to x: 0.5, y:0 and has a rect transform position of x: 0, y: 44.

Those display just fine, the anchoring works well across screen sizes.

I want the destination of my "tween"ed sprite to be the “Hotbar” image. 0, 0 takes my tweened image to the bottom left corner of the window, no matter how I adjust the anchor. I can’t figure out any coordinate system to make these all make sense. The “WorldToScreenPoint” position seems correct, the canvas and hotbar graphics show correctly, so I’m not sure what’s missing.

The hotbar position is x: 608.5 y: 44, it’s localPosition is x: 0, y: 44.

If 0, 0 for my tweened sprite is “lower left corner” then none of those coordinate systems are right.

// Convert this world coord to a screen coord
var camera = GameObject.Find("Main Camera").GetComponent<Camera>();
var screenPos = camera.WorldToScreenPoint((Vector3)tile.worldVec);

// We'll render to the HUD canvas
var canvas = GameObject.Find("HUDCanvas");

// Build a game object with the sprite
var gameObject = new GameObject();
gameObject.AddComponent<RectTransform>();
gameObject.AddComponent<Image>();
gameObject.GetComponent<Image>().sprite = drop.gameObject.GetComponent<SpriteRenderer>().sprite;
gameObject.transform.position = screenPos;
gameObject.transform.SetParent(canvas.transform);
gameObject.GetComponent<RectTransform>().anchoredPosition = new Vector2(0.5f, 0);

var hotbar = GameObject.Find("Hotbar");

// Tween it
LeanTween.move(gameObject, new Vector3(0, 0, 0), 0.25f).setEase( LeanTweenType.easeInQuad ).setOnComplete(() => {
    Destroy(gameObject);
});

Can’t you use; Unity - Scripting API: RectTransformUtility to do the conversions?

6 Likes

Thanks, that worked. I was able to use RectTransformUtility.PixelAdjustPoint to convert the hotbar transform to a usable screen/pixel value.

4 Likes

How did you do that? Can you post the code?

11 Likes

For finding the local coordinates of a child RectTransform in regards to the mouse I used this:

            Vector3[] corners = new Vector3[4];
            colorPickerRT.GetWorldCorners(corners);
            Vector2 mousePos = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
            Vector2 localImagePos = (mousePos - (Vector2)corners[0]) / mainUIRT.localScale.x;
3 Likes

I found this post while struggling to get a UI element to tween in local space (using its anchoredPosition) to the position of another (non-sibling) element.

Here’s the easiest way I found.

First get the desired offset to the destination object in world space:

var toDestinationInWorldSpace = destinationTransform.position - tweeningTransform.Position;

Then translate the world space offset into the tweening object’s local space using the tweening object’s transform:

var toDestinationInLocalSpace = tweeningTransform.InverseTransformVector(toDestinationWorld);

Then you can tween or set your tweeningTransform.anchoredPosition to toDestinationInLocalSpace.

See InverseTransformVector docs

5 Likes

Thank you so much @edwardrowe … i was stuck in that for 3 days… really appreciate it

2 Likes

Thanks, I finally used this set of stuff and it worked for me.

I was trying to make the UI Button spawn at the position of a 2D SpriteRenderer. The script was attached to the GameObject with the SpriteRenderer

public class AttachButton : MonoBehaviour
{
    [SerializeField] RectTransform buttonTransform;
    [SerializeField] RectTransform canvasTransform;

    private void Awake()
    {
        Vector3 screenPos = Camera.main.WorldToScreenPoint(this.transform.position);
        Vector2 screenPos2D = new Vector2(screenPos.x, screenPos.y);
        Vector2 anchoredPos;
        RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasTransform, screenPos2D, Camera.main, out anchoredPos);
        buttonTransform.anchoredPosition = anchoredPos;
    }
}
2 Likes

It did not work for me, I had to adjust the position of that offset, like this

public class Test : MonoBehaviour
{
    public RectTransform targetRect;
    public RectTransform elementRect;

    public void GO(){
        var toDestinationInWorldSpace = targetRect.position - elementRect.position;
        var toDestinationInLocalSpace = elementRect.InverseTransformVector(toDestinationInWorldSpace);    
        elementRect.anchoredPosition = elementRect.anchoredPosition + (Vector2)toDestinationInLocalSpace;
    }

}

But hey, thanks a lot!

EDIT:

using DoTween is even more elegant! Cheers

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class MoveUIElementToUIElement : MonoBehaviour
{
    public RectTransform targetRect;
    public RectTransform elementRect;

    public void GO()
    {
        var toDestinationInWorldSpace = targetRect.position - elementRect.position;
        var toDestinationInLocalSpace = elementRect.InverseTransformVector(toDestinationInWorldSpace);
        elementRect.DOAnchorPos(toDestinationInLocalSpace,1f).SetRelative(true);
    }
}
1 Like