Converting Canvas Space to Screen Space

Hey all,

I am very new to Unity and my math skills aren’t fantastic so I apologise in advance for the newbie question :wink:

Scenario:

I have a camera in my scene and I need to adjust it’s viewport as the screen resizes in order to place it correctly within the surrounding UI. I decided to add a container to my UI canvas so that this can be repositioned and sized automatically by Unity. Then my plan was to simply make the cameras viewport position and size match it.

In order to achieve this I decided to make the below component to handle the repositioning and resizing.

I am working in 2D and therefore using an orthographic camera, the UI is in Screen Overlay mode with a pixel perfect canvas. There is also a canvas scaler set to scale to screen size with a reference resolution of 1920x1080.

Code:

Note that I have removed contractual (null) checks from Awake to make the example more concise:

public class ScaleCameraViewportToCanvasTarget : MonoBehaviour
{
    // Unity visible properties
    public GameObject ScaleToFit; 
    //

    private Camera cameraToScale;
    private RectTransform targetTransform;
    private Canvas canvas;

    public void Awake()
    {
        this.targetTransform = ScaleToFit.GetComponent<RectTransform>();
        this.canvas = ScaleToFit.GetComponentInParent<Canvas>();
        this.cameraToScale = GetComponent<Camera>();
    }

    public void LateUpdate()
    {
        // Determine target dimensions taking in to consideration canvas scaling
        var width = targetTransform.rect.width * canvas.scaleFactor;
        var height = targetTransform.rect.height * canvas.scaleFactor;

        // Get centre point of our target in Canvas Space
        var centreX = targetTransform.anchoredPosition.x;
        var centreY = targetTransform.anchoredPosition.y;

        // Adjust for anchor from centre to upper left
        var left = centreX - (width * 0.5f);
        var top = centreY - (height * 0.5f);

        // Convert from Canvas Space to Screen Space
        left = left + (Screen.width * 0.5f);
        top = top + (Screen.height * 0.5f);

        // Adjust camera viewport
        var targetRect = new Rect(left, top, width, height);
        this.cameraToScale.pixelRect = targetRect;
    }
}

Problem:

This does kinda work but it isn’t accurate. When canvas scaling is off everything works as expected but when it is enabled the width and height are correct but the left, top positioning isn’t correct.

For example:

X is -0.03813588 when it should be 0.0

Y is 0.04876541when it should be 0.03876541 (±)

Any ideas? I have spent a lot of time on this and I can’t help but think I am doing something fundamentally wrong.

Many thanks in advance,

Mark

Hi all,

I have worked out the issue myself. I wasn’t adjusting the position by the scale factor. Applying the factor to the anchored position prior to manipulation fixes the issue:

        var centreX = targetTransform.anchoredPosition.x * canvas.scaleFactor;
        var centreY = targetTransform.anchoredPosition.y * canvas.scaleFactor;

Many Thanks,

Mark

For future reference, this is the code I use to do the same thing.

Whenever your RectTransform resizes, it’s going to trigger OnRectTransformDimensionsChange. You can use this to do your camera viewport adjustments. So, you can put this code on your RectTransform:

[RequireComponent(typeof(RectTransform))]
public class CameraViewSizer : MonoBehaviour
{
    [SerializeField]
    private Camera _camera;

    protected void OnRectTransformDimensionsChange()
    {
        _camera.pixelRect = Util.RectTransformToScreenSpace((RectTransform)this.transform);
    }
}

This is the RectTransformToScreenSpace function used above:

public static class Util
{
    public static Rect RectTransformToScreenSpace(RectTransform transform)
    {
        Vector2 size = Vector2.Scale(transform.rect.size, transform.lossyScale);
        return new Rect((Vector2)transform.position - (size * 0.5f), size);
    }
}