Search code examples
user-interfacecanvasresolutionunity-game-engine

UI Elements not scaling while using Canvas Scaler


I'm trying to make my UI elements work and remain the same for every different resolution. I added a Canvas Scaler to my Canvas and played around with the settings until it looked finished.

I then tried building the game and running it at few different resolutions to confirm that it was working. However, the Canvas Scaler doesn't seems to work.

http://prntscr.com/d1afz6

Above is some random resolution but that's how big my editor screen is and that's what I'm using as my reference resolution. That's also the hierarchy for this specific Canvas http://prntscr.com/d1aggx. It takes almost the whole screen when ran at 640x480. I have no clue why this is not working. I've read most of the unity guides on that but none of them seem to have that problem.


Solution

  • Ok, to fit something no matter the size of the screen, you have to use a separate coordinate system than Unity's absolute system. One of Unity's models is the View. The View is coordinates 0,0 at the top left, and 1,1 at the bottom right. Creating a basic Rect that handles that, is the following.

    using UnityEngine;
    
    namespace SeaRisen.nGUI
    {
        public class RectAnchored
        {
            public float x, y, width, height;
    
            public RectAnchored(float x, float y, float width, float height)
            {
                this.x = x;
                this.y = y;
                this.width = width;
                this.height = height;
            }
    
            public static implicit operator Rect(RectAnchored r)
            {
                return new Rect
                {
                    x = r.x * Screen.width,
                    y = r.y * Screen.height,
                    width = r.width * Screen.width,
                    height = r.height * Screen.height
                };
            }
        }
    }
    

    Here, we take the normal Rect floats, the x,y coordinates along with a width and height. But these are in the values [0..1]. I don't clamp it, so it can be tweened on and off the screen with animation, if desired.

    The following is a simple script that create's a button in the lower right corner of the screen, and resizes as the screen grows or shrinks.

    void MoveMe()
    {
        RaycastHit hit;
        if (Physics.Raycast(transform.position, -Vector3.up, out hit, float.MaxValue)
            || Physics.Raycast(transform.position, Vector3.up, out hit, float.MaxValue))
            transform.position = hit.point + Vector3.up * 2;
    }
    
    void OnGUI()
    {
        if (GUI.Button(new RectAnchored(.9f, .9f, .1f, .1f), "Fix me"))
        {
            MoveMe();
        }
    }
    

    The X is .9 to the right and Y .9 from the top, and width and height of .1, so the button is 1/10th of the screen in height and width, positioned in the bottom 1/10th of the screen.

    Since OnGUI is rendered every frame (or so), the button rect updates with the screen resize automatically. The same would work in a typical UI, if you are using Update() to render your windows.

    I hope this explains the difference between what I meant with absolute coordinates. Setting say the previous example (using absolutes) in 640x480, it'd be something like new Rect(576, 432, 64, 48) and it wouldn't scale. By using new RectAnchored(.9f, .9f, .1f, .1f) and have it rendered into UI space based on Screen size, then it scales automatically.