Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Resolution and scaling - screen, font and sliced image problems.

Discussion in 'UGUI & TextMesh Pro' started by Aithoneku, Aug 23, 2014.

  1. Aithoneku

    Aithoneku

    Joined:
    Dec 16, 2013
    Posts:
    67
    (I hope it's ok I put a few semi-related problems into single thread instead of spamming the forum.)

    I'm trying to figure out how to prepare my application for different resolutions as much as possible. I found a solution, which is fitting for me, however I found a few problems along the way so I would like to share.

    Reference resolution

    This is very cool component, similar to something in NGUI, however I there is a problem when switching between both horizontally and vertically oriented screens. (I encountered this on my friend's Android device when I tested my app.)

    Let's say I setup reference resolution to 1024x768 and set match width or height to 1. When screen is wider then 4:3, everything is visible well and I have free space in left and right sides. For now, all good. However, when screen is oriented vertically, I have much less space in horizontal sides then 768px. If I set match width or height to 0, it's exactly the opposite. If I set the ratio between 0 and 1, both problems occurs in both cases except exact screen aspect ratio.

    Example screenshots:
    _01.png

    Notes:
    • Background is consisting of two images. Red one with size 1024x768 and anchored to the center, and green one, anchored to the red one with 10px offset from borders.
    • As you can see, the GUI elements size is not reflecting size of the screen, but only one dimension.
    • Even if we would set match width or height to 0.5 in order to get average result, with screen aspect ratio too much different from the reference one, the more this problem appears again.
    • Of course I'm aware of anchoring items to corners and sides instead of center, however look at screenshots again - in one case, button's size is ok, in the other it covers almost whole screen!
    • You might object that these wide screens are too extreme - no, they're not. My friend's Android suffers with exactly this problem! Horizontal screen was ok, but when I rotated the device, elements were too big!

    And finally, my solution: it's simple. Just scale the canvas so area of reference resolution is visible all the time.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. ///
    5. [RequireComponent(typeof(Canvas))]
    6. [ExecuteInEditMode]
    7. public class ScreenResolutionManager : MonoBehaviour
    8. {
    9.     [SerializeField]
    10.     [Tooltip("Reference resolution (for which resolution was GUI made).")]
    11.     private Vector2 _ReferenceResolution = new Vector2(800, 600);
    12.    
    13.     [SerializeField]
    14.     [Tooltip("Scale of reference gui. Use it in order to make GUI bigger / smaller.")]
    15.     private float _GuiScale = 1.0f;
    16.    
    17.     /// Controlled canvas.
    18.     private Canvas _Canvas;
    19.    
    20.     /// Scale of reference gui. Use it in order to make GUI bigger / smaller.
    21.     public float GuiScale
    22.     {
    23.         get { return _GuiScale; }
    24.         set { _GuiScale = value; ScaleUpdate(); }
    25.     }
    26.    
    27.    
    28.    
    29.     protected void Awake()
    30.     {
    31.         _Canvas = GetComponent<Canvas>();
    32.     }
    33.    
    34.     protected void Start()
    35.     {
    36.         ScreenManager.Get().OnScreenSizeChanged += SetNewResolution;
    37.         SetNewResolution(Screen.width, Screen.height);
    38.     }
    39.    
    40.    
    41.     #if UNITY_EDITOR
    42.    
    43.     protected void Update()
    44.     {
    45.         if(!Application.isPlaying)
    46.         {
    47.             _Canvas = GetComponent<Canvas>();
    48.             ScaleUpdate();
    49.         }
    50.     }
    51.    
    52.     #endif
    53.    
    54.     protected void SetNewResolution(int width, int height)
    55.     {
    56.         ScaleUpdate();
    57.     }
    58.    
    59.     protected void ScaleUpdate()
    60.     {
    61.         float width = (float)Screen.width;
    62.         float height = (float)Screen.height;
    63.        
    64.         float widthScale = width / _ReferenceResolution.x;
    65.         float heightScale = height / _ReferenceResolution.y;
    66.        
    67.         _Canvas.scaleFactor = Mathf.Min(widthScale, heightScale) * GuiScale;
    68.     }
    69.    
    70. }
    71.  
    Now resulting screenshots:
    _02.png

    Everything now looks ok. There is only one slight problem you might notice: in vertical screenshot, the button is bigger. This can be solved by using reference resolution with aspect ratio 1:1, for example 1024x1024. That's my solution as I want support both horizontal and vertical screens.

    Physical resolution

    Amazing idea - keep the GUI elements with same physical size. However, the result looks awful (for some units):
    _03.png

    My configuration:
    • Desktop with DPI 96
    • Units: millimeters
    • Image size: 130x37 (units = mm)
    • Font size: 16 (pt)

    Source of the problem is obvious: the component "just" scales the canvas so one unit is equal to one millimeter, however slicing system thinks that one unit is still one pixel and so the text does. This problem is discussed bellow, in next part.

    Scaling text and sliced sprites

    Problem is described in upper part (Physical resolution), however it can be encountered in multiple cases:
    • Reference resolution - it also "just" scales GUI, so if the referenced resolution is low (let's say 320x240), exactly this problem arises on high-resolution screen - text is ugly and so are the sliced images. However, if I use big referenced resolution (let's say more that 1280x1280) and my actual screen size is much smaller (like 800x600), text is scaled down consuming lot of memory and looks ugly as it uses (AFAIK) bilinear filtering. (Exactly same thing happens with sliced image if it doesn't use trilinear filtering - in this case it consumes even more memory and power.)
    • Physical resolution - see part above.
    • Scaling in general - as stated in part above, this problem occurs each end every time GUI is scaled. I'm aware that scaling is somehow discouraged (except animations and mirroring), however it's unavoidable in case of keeping reasonable size of GUI elements. Also, what if we want to give user option to double size of the GUI elements?

    Solution is not much clear. IMHO the best solution needs to be done in the Unity UI itself:
    • When text is scaled, instead of scaling resulting text model, scale font size. (I think best would be to make an option which behavior should be applied.)
    • When image is sliced, two approaches can be applied:
      • Ignore scale value for size of corners and always make it pixel-perfect. Size of the resulting image should be, of course, considering the scale as it's now.
      • Define separate scale value which would be applied on size of the corners. It's something like extension of the previous suggestion.

    Of course, I have no idea whether Unity developers will ever fix this (I'm applying "don't rely on something you don't have" philosophy), so another solutions are:
    • Use reference resolution between reasonable expected minimum and maximum. With this we will punish people for having too small or too big resolution, but in average it will look somehow good.
    • Instead of single reference resolution, use an interval. Let's say between 800x600 and 1280x1024. When user's resolution will be between these values, no scale will be applied (it would be just 1.0f). If the resolution is lower, it will be scaled down and if it's larger, it will be scaled up. I think this solution should be applied anyway (users can use of more place on their screen, but the game will remain usable for too different sizes). This still doesn't solve too big/small screen problems and also different dpi.
     
    Ash-Blue, tsubaki_t1 and thempus like this.
  2. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,877
    This is how it's designed to work. With matchWidthOrHeight set to 0.5 the area (in square units) of the canvas stays approximately the same, so wider width is compensated with shorter height and vice versa. This way UI elements stay at about the same size relative to the size (area) of the screen.

    Of course this doesn't fit every use case. In your case you prefer that any additional space outside of a square always only expands the canvas area and never contracts it in either direction. This behavior has the effect that the more extreme the aspect ratio (bigger difference between width and height), the smaller the UI elements appear relative to the screen area (in square inches or whatever units you prefer to use). That's fine too, and as you saw yourself, it's not too difficult to create a script that caters to your own desired behavior.

    We've mentioned it in a few threads, but essentially the UI system doesn't have a great solution for scaling in Unity 4.6. This is something we want to improve upon in future releases.

    There are however workarounds you can work with. Every Image and Text component has a pixelsPerUnit property that can be set from script which can be used to make it use more than one texel per unit. (In the case of Image it's only relevant for the Sliced or Tiled option.) Why not set these automatically based on the Canvas Scale Factor? Because it's not always that simple. There may be other scaling in effect besides the scale factor, and for perspective UI the scale on the screen can even be different for different parts of the same graphic. Also, due to limitations of our text renderer, setting the scaleFactor on text can make the result subtly different with regards to where word wrapping occurs, so it can actually change the layout. Again, a rewritten text renderer is planned but will come at a later point after 4.6.
    You can do this by using the pixelsPerUnit setting on the Text.
    You can do this by using the pixelsPerUnit setting on the Image.
     
    Ash-Blue likes this.
  3. Aithoneku

    Aithoneku

    Joined:
    Dec 16, 2013
    Posts:
    67
    I see, I guess I'm too perfectionist for this :D.

    I see. I saw this mentions in threads, but I somehow missed that the problem is acknowledged and planned to be improved in future. That's all I need :).

    Ah! I guess I didn't pay attention during reading documentation, I hadn't idea something like this is there! I only knew about pixelsToUnit in sprite settings (which wasn't affecting the gui at all) and I didn't notice that this is there!

    That's why I would suggest to make it optional. My apologizes if I didn't make myself clear in this regard. (I don't consider this behavior to be forced as good design.)

    Yeah, I encountered this in past project, but I think that optionality and layout system can keep it well.


    Anyway, thank you for your answers very much.
     
    tsubaki_t1 likes this.
  4. Ash-Blue

    Ash-Blue

    Joined:
    Aug 18, 2013
    Posts:
    102
    How exactly does one set the pixels per unity property? Its set to read only. Example code if someone could please help.
     
  5. runevision

    runevision

    Joined:
    Nov 28, 2007
    Posts:
    1,877
    You're replying to an old thread that was from when the UI system was in beta.

    In the released version you control the pixels per unit in the import settings of the sprites.
     
    Ash-Blue likes this.
  6. Aithoneku

    Aithoneku

    Joined:
    Dec 16, 2013
    Posts:
    67
    I'm really glad you changed the behaviour at the end; I want to thank you very much for that!
     
  7. jrhowa

    jrhowa

    Joined:
    Feb 3, 2015
    Posts:
    5
    Where are you getting this ScreenManager.Get() OnScreenSizeChanged ? Would be a great call back to have?

    - j
     
  8. Aithoneku

    Aithoneku

    Joined:
    Dec 16, 2013
    Posts:
    67
    Sorry about that - that's my own code, but it's simple. It stores current resolution and each and every frame in Update it checks whether the resolution changed. If it does, call the delegate.