Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Feature Request Limiting "Scale With Screen Size" to integer values

Discussion in 'UGUI & TextMesh Pro' started by ben4d85, Apr 24, 2023.

  1. ben4d85

    ben4d85

    Joined:
    Dec 26, 2018
    Posts:
    47
    I believe I noticed a problem with the CanvasScaler component, when used for 2D pixel art games.

    If the CanvasScaler's "UI Scale Mode" is set to "Scale With Screen Size", it will set the "Scale" property of the RectTransform to float values (e.g. 2.133). This happens no matter which option is selected for the "Screen Match Mode". And it happens even if the Canvas's "Pixel Perfect" option is checked.

    I believe it would be beneficial if Unity would add an option to limit the calculation to only allow integer values for the "Scale" property, e.g. 2 instead of 2.1333.

    Logically, this is what I would expect to happen if the Canvas's "Pixel Perfect" option is checked. But as of yet it does not. So Unity could either use that checkbox to determine whether or not to use integer Scales, or make a separate checkbox in the CanvasScaler component.

    I understand that the "Constant Pixel Size" option allows manually setting the "Scale Factor". However, this is fixed and does not scale up if the resolution is changed. So it is of limited benefit.

    PS: If anybody knows of a custom canvas scaler component that does this, let me know.

    ---


    Update: Here's the basic idea in code. Unfortunately, this does not change the scale of the canvas. I'm not sure why; perhaps canvas scale needs to be set in some special way?

    Code (CSharp):
    1. var scaleX = Screen.width / referenceResolution.x;
    2. var scaleY = Screen.height / referenceResolution.y;
    3.  
    4. var scaleChosen = Mathf.Min(scaleX, scaleY);
    5. var scaleChosenInt = Mathf.FloorToInt(scaleChosen);
    6.  
    7. var rectTransform = GetComponent<RectTransform>();
    8. rectTransform.localScale = new Vector2(
    9.     scaleChosenInt,
    10.     scaleChosenInt
    11. );
     
    Last edited: Apr 24, 2023
  2. karliss_coldwild

    karliss_coldwild

    Joined:
    Oct 1, 2020
    Posts:
    595
    Canvas "Pixel perfect" option is completely unrelated to pixelart meaning of "pixel perfect".

    It snaps the positions to screen pixels which can helpful for reducing certain edge blurriness or <1px gaps even when using high resolution images.

    Some of the options for pixelart UI:
    * If you are using a pixelperfect camera for non UI stuff, you can make a very simple component which reads the current integer scale factor from pixelperfect camera and applies it to canvas.

    * Make your own integer scaling logic similar to code you pasted.

    You probably need to use Canvas.scaleFactor property instead of touching rectTransform, that's what the Unity CanvasScaler does. Wanted to remind you that the source code for UGUI is available and you can read what and how most of it works. Just right click on a component in "Inspector" and choose "Edit Script" just like you would do for your own scripts.

    Probably somewhat redundant, but you can also manipulate the scale factor Canvas Scaler which is in "Constant Pixel Size" mode using code.
     
    ben4d85 likes this.
  3. ben4d85

    ben4d85

    Joined:
    Dec 26, 2018
    Posts:
    47
    Thank you so much, I had no idea that code was available to look at!

    Given the learnings from the above, the following code appears to solve the problem for me. It should be noted that this is very simple and doesn't take into account dpi, multiple monitors, different types of scaling and everything else Unity's CanvasScaler does.

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class CanvasScalerInteger : MonoBehaviour
    4. {
    5.     [SerializeField] private Vector2 referenceResolution;
    6.  
    7.     private void Start()
    8.     {
    9.         var scaleX = Screen.width / referenceResolution.x;
    10.         var scaleY = Screen.height / referenceResolution.y;
    11.  
    12.         var scaleChosen = Mathf.Min(scaleX, scaleY);
    13.         var scaleChosenInt = Mathf.FloorToInt(scaleChosen);
    14.  
    15.         var canvas = GetComponent<Canvas>();
    16.         canvas.scaleFactor = scaleChosenInt;
    17.     }
    18. }
    Note: I haven't tested if this updates when I change the resolution in the Editor (as I moved on to the solution discussed below). It probably doesn't.
     
    Last edited: Apr 24, 2023
  4. ben4d85

    ben4d85

    Joined:
    Dec 26, 2018
    Posts:
    47
    Yes, I am using a PixelPerfectCamera component on my Main Camera! That's a great idea; it seems like a more consistent approach.

    Here's my code for that. The event subscription is to ensure that the canvas' scale factor gets updated when I change the resolution in the Editor. Although there is a slight delay. But without this, it would only update when entering Play Mode.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Experimental.Rendering.Universal;
    3.  
    4. [RequireComponent(typeof(Canvas))]
    5. [ExecuteAlways]
    6. [DisallowMultipleComponent]
    7. public class CanvasScalerWithPixelPerfectCamera : MonoBehaviour
    8. {
    9.     private Canvas canvas;
    10.     private PixelPerfectCamera pixelPerfectCamera;
    11.  
    12.     private void OnEnable()
    13.     {
    14.         canvas = GetComponent<Canvas>();
    15.         pixelPerfectCamera = Camera.main.GetComponent<PixelPerfectCamera>();
    16.    
    17.         HandleWillRenderCanvases();
    18.  
    19.         Canvas.preWillRenderCanvases += HandleWillRenderCanvases;
    20.     }
    21.  
    22.     private void OnDisable()
    23.     {
    24.         Canvas.preWillRenderCanvases -= HandleWillRenderCanvases;
    25.     }  
    26.  
    27.     private void HandleWillRenderCanvases()
    28.     {
    29.         // Guard (just in case)
    30.         if (pixelPerfectCamera == null) return;
    31.  
    32.         try
    33.         {
    34.             // Update the canvas' scale factor
    35.             canvas.scaleFactor = pixelPerfectCamera.pixelRatio;
    36.         }
    37.         // Because in PixelPerfectCamera.cs, line 249, m_Internal could be null
    38.         // and they don't have a guard for that
    39.         catch (System.NullReferenceException)
    40.         {
    41.             // Log
    42.             Debug.Log("Pixel perfect camera's pixel ratio not (yet) available.");
    43.         }
    44.     }
    45. }
     
    Last edited: Apr 25, 2023
  5. ben4d85

    ben4d85

    Joined:
    Dec 26, 2018
    Posts:
    47
    Overall, I still think Unity should just add a checkbox for this to their existing CanvasScaler component. That would be a lot more convenient and avoid people having to create their own.