Search code examples
c#unity-game-engineprojectionunity-ui

Get a UnityEngine.UI.Images position in screenSpace and calculate a normalised offset (inside an overlay canvas)


The Problem at hand:

Simplified

Given an UnityEngine.Ui.Image How does one find the X,Y position of a normalised offset (like 0.4, 0.3 from the top left) inside that image in ScreenSpace units like 400,300

I guess I need to find the top left ScreenSpace value and then knowing the rendered total size of the image scale the normalised offsets by the actual size ratio expressed in pixels.

Figure 1: Figure 1:

Figure 2 shows the normalisedOffsets that are to be used

Figure 2: An example stored Rect "offset"

So, in precis, I need to find the offset in ScreenSpace pixels of the topLeft of the Rect I have stored against the image.

I recognise it is probably a combination of Camera.main.ViewportToWorldPoint() and some reference to the bounds, possibly scaling that by backgroundImage.sprite.pixelsPerUnit?

Struggling to visualise how to exactly get this done.

thanks


Solution

  • I guess you don't have any scale or rotation in the image parents and the position Y is 0.

    First you can get the position of the upper left corner of your image with rectTransform.GetWorldCorners():

        //Upper left corner
        Vector3[] v = new Vector3[4];
        image.rectTransform.GetWorldCorners(v);
        var recPos = v[1];
    

    Then you have to transform your normalized offset to a world space offset by a ratio between your image size and your rect size and add the position of the top left corner:

        var recWidth = image.rectTransform.sizeDelta.x;
        var imgWidth = image.sprite.texture.width;
        var realOffsetX = offsetX * (recWidth / imgWidth);
        var realPosX = recPos.x + realOffsetX;
    

    (It is the same formula for the Y coordinate but you have to subtract by your ratio realOffsetY because the offset is calculated from the top left corner)

    Here is the full method:

    private Vector3 GetPositionOffset(Image image, float offsetX, float offsetY)
    {
        //Upper left corner
        Vector3[] v = new Vector3[4];
        image.rectTransform.GetWorldCorners(v);
        var recPos = v[1];
    
        //X coordinate
        var recWidth = image.rectTransform.sizeDelta.x;
        var imgWidth = image.sprite.texture.width;
        var realOffsetX = offsetX * (recWidth / imgWidth);
        var realPosX = recPos.x + realOffsetX;
    
        //Y coordinate
        var recHeight = image.rectTransform.sizeDelta.y;
        var imgHeight = image.sprite.texture.height;
        var realOffsetY = offsetY * (recWidth / imgWidth);
        var realPosY = recPos.y - realOffsetY;
    
        //Position
        return new Vector3(realPosX, realPosY, image.transform.position.z);
    }
    

    Then if you want this World space to screen space just use the camera method:

    camera.WorldToScreenPoint(positionOffset);