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.
Dismiss Notice
Join us now in the Performance Profiling Dev Blitz Day 2023 - Q&A forum where you can connect with our teams behind the Memory and CPU Profilers and the Frame Debugger.

Question UI Scaling issues only with QHD and 4k UHD

Discussion in 'UGUI & TextMesh Pro' started by slpearlman, Jan 28, 2023.

  1. slpearlman

    slpearlman

    Joined:
    Aug 29, 2014
    Posts:
    8
    I have a card game, where I'd like to be able to zoom in on a card if it's right clicked. I coded to make sure that if the zoomed card would go over a screen boundary, instead we would anchor it such that the full card is displayed. I initially hardcoded everything for 1920x1080 resolution, then I wanted to get it to work for an arbitrary resolution. The logic seems to work for all resolutions except for QHD and 4k UHD.

    I feel like I have a fundamental misunderstanding of resolutions, or I'm missing something with either how RectTransform scaling works, or how the units for width/height correspond to resolutions, or how the interaction between Canvas and CanvasScaler work with resolutions. The math seems simple, but it's not working for these resolutions, so I'm obviously missing something.

    For reference:


    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3.  
    4. public class Zoomable : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
    5. {
    6.     private GameObject _popOutCard;
    7.     private bool _enabled;
    8.  
    9.     // Works for all resolutions except QHD and 4k UHD.
    10.     // Calculations: Card Dimensions are 150 x 200.
    11.     // So, if we're, for example, scaling by a factor of 2, the zoomed cards are 300 x 400.
    12.     // We're using the (x,y) coordinates of the cursor.
    13.     // So for 1920 x 1080, for example:
    14.     // To display a zoomed card, its center with respect to x can never be less than 155 (300/2, with an offset of 5).
    15.     // The same logic is applied to the other 3 boundaries.
    16.  
    17.     private const int _offset = 5;
    18.     private const int _cardHeightHalfed = 100;
    19.     private const int _cardWidthHalfed = 75;
    20.  
    21.     private float _topBoundary;
    22.     private float _bottomBoundary;
    23.     private float _leftBoundary;
    24.     private float _rightBoundary;
    25.  
    26.     [SerializeField]
    27.     private float _scaleFactor = 2.5f;
    28.  
    29.     private Transform _canvasTransform;
    30.  
    31.     void Awake()
    32.     {
    33.         // We're mixing ints and floats here, but it's fine.
    34.         _canvasTransform = transform.root;
    35.         _topBoundary = Screen.height - _scaleFactor * _cardHeightHalfed - _offset;
    36.         _bottomBoundary =_scaleFactor * _cardHeightHalfed + _offset;
    37.         _leftBoundary = _scaleFactor * _cardWidthHalfed + _offset;
    38.         _rightBoundary = Screen.width - _scaleFactor * _cardWidthHalfed - _offset;
    39.         var resolution = Screen.currentResolution;
    40.  
    41.         Debug.Log($"Resolution height: {resolution.height}, resolution width: {resolution.width}");
    42.         Debug.Log($"Screen height: {Screen.height}, resolution width: {Screen.width}");
    43.     }
    44.     public void OnPointerDown(PointerEventData eventData)
    45.     {
    46.         if (eventData.button == PointerEventData.InputButton.Right)
    47.         {
    48.             ZoomPopOut();
    49.         }
    50.     }
    51.  
    52.     public void OnPointerUp(PointerEventData eventData)
    53.     {
    54.         Debug.Log("OnPointerUp called");
    55.         if (_enabled)
    56.         {
    57.             OnZoomPopOutExit();
    58.         }
    59.     }
    60.  
    61.     private void ZoomPopOut()
    62.     {
    63.         float xPosition = Input.mousePosition.x;
    64.         float yPosition = Input.mousePosition.y;
    65.         Debug.Log($"x position: {xPosition}, y position: {yPosition}");
    66.  
    67.         float popOutXPosition = xPosition;
    68.         float popOutYPosition = yPosition;
    69.  
    70.         if (xPosition < _leftBoundary)
    71.         {
    72.             popOutXPosition = _leftBoundary;
    73.         }
    74.  
    75.         if (xPosition > _rightBoundary)
    76.         {
    77.             popOutXPosition = _rightBoundary;
    78.         }
    79.  
    80.         if (yPosition < _bottomBoundary)
    81.         {
    82.             popOutYPosition = _bottomBoundary;
    83.         }
    84.  
    85.         if (yPosition > _topBoundary)
    86.         {
    87.             popOutYPosition = _topBoundary;
    88.         }
    89.  
    90.         Debug.Log($"Pop out x position: {popOutXPosition}, pop out y position: {popOutYPosition}");
    91.  
    92.         _popOutCard = Instantiate(gameObject, new Vector2(popOutXPosition, popOutYPosition), Quaternion.identity);
    93.         _popOutCard.transform.SetParent(_canvasTransform);
    94.         _popOutCard.transform.SetAsLastSibling();
    95.  
    96.         RectTransform rect = _popOutCard.GetComponent<RectTransform>();
    97.         rect.localScale = new Vector3(_scaleFactor, _scaleFactor, _scaleFactor);
    98.         _enabled = true;
    99.     }
    100.  
    101.     private void OnZoomPopOutExit()
    102.     {
    103.         Debug.Log("OnZoomPopOutExit() called");
    104.         _enabled = false;
    105.         Destroy(_popOutCard);
    106.     }
    107. }
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    32,371
    Just a heads-up, it is HARD to manipulate the quantities in any of the UnityEngine.UI objects unless you only drive them to known "invisible marker" positions within the UI, such as tweening between two known locations.

    Good anchoring and scaling done in the editor is always your best choice.

    Otherwise, your code issue is likely in the anchoring, and even anchoring takes SO many lines of code to do. Check this for how much it takes just to put the anchors in your own corners:

    http://forum.unity3d.com/threads/sc...hor-to-gui-object-size-rect-transform.269690/
     
  3. slpearlman

    slpearlman

    Joined:
    Aug 29, 2014
    Posts:
    8
    I didn't realize that I needed to map from screen space (mouse coordinates) to canvas space in my original code. After learning about `RectTransformUtility.ScreenPointToLocalPointInRectangle` and realizing that `Instantiate` always anchors to the center of the canvas, I also realized that I should modify the border calculations to account for the anchoring, and move the constraining borders code to after the screen space to canvas space mapping.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine;
    3. using UnityEngine.EventSystems;
    4. public class Zoomable : MonoBehaviour, IPointerDownHandler, IPointerUpHandler
    5. {
    6.     private GameObject _popOutCard;
    7.     private bool _enabled;
    8.     // Mouse Coordinates are given in screen space.
    9.     // We then map those coordinates to canvas space, which is set to
    10.     // 1920 x 1080.
    11.     // Calculations: Card Dimensions are 150 x 200.
    12.     // So, if we're, for example, scaling by a factor of 2, the zoomed cards are 300 x 400.
    13.     // So for 1920 x 1080:
    14.     // To display a zoomed card, its center with respect to x can never be less than 155 (300/2, with an offset of 5) away
    15.     // from a left/right boundary.
    16.     // The same logic is applied to the top/bottom boundaries.
    17.     // Since the anchoring of the popOutCard with respect to the canvas is centered, i.e. (0,0) is the center of the canvas
    18.     // we need to account for that when calculating the boundaries in Awake.
    19.     private const int _offset = 5;
    20.     private const int _cardHeightHalfed = 100;
    21.     private const int _cardWidthHalfed = 75;
    22.     private const int _canvasHeightHalfed = 540;
    23.     private const int _canvasWidthHalfed = 960;
    24.     private float _topBoundaryCanvas;
    25.     private float _bottomBoundaryCanvas;
    26.     private float _leftBoundaryCanvas;
    27.     private float _rightBoundaryCanvas;
    28.     [SerializeField]
    29.     private float _scaleFactor = 2.5f;
    30.     private Transform _canvasTransform;
    31.     void Awake()
    32.     {
    33.         _canvasTransform = transform.root;
    34.         // We're mixing ints and floats here, but it's fine.
    35.         _topBoundaryCanvas = (_canvasHeightHalfed - _scaleFactor * _cardHeightHalfed - _offset);
    36.         _bottomBoundaryCanvas = (_canvasHeightHalfed - _scaleFactor * _cardHeightHalfed - _offset)*-1;
    37.         _leftBoundaryCanvas = (_canvasWidthHalfed - _scaleFactor * _cardWidthHalfed + _offset)*-1;
    38.         _rightBoundaryCanvas = _canvasWidthHalfed - _scaleFactor * _cardWidthHalfed - _offset;
    39.     }
    40.     public void OnPointerDown(PointerEventData eventData)
    41.     {
    42.         if (eventData.button == PointerEventData.InputButton.Right)
    43.         {
    44.             ZoomPopOut();
    45.         }
    46.     }
    47.     public void OnPointerUp(PointerEventData eventData)
    48.     {
    49.         Debug.Log("OnPointerUp called");
    50.         if (_enabled)
    51.         {
    52.             OnZoomPopOutExit();
    53.         }
    54.     }
    55.     private void ZoomPopOut()
    56.     {
    57.         float xPosition = Input.mousePosition.x;
    58.         float yPosition = Input.mousePosition.y;
    59.         Debug.Log($"x position: {xPosition}, y position: {yPosition}");
    60.         Vector2 convertedPosition;
    61.         RectTransformUtility.ScreenPointToLocalPointInRectangle((RectTransform)_canvasTransform,
    62.                                                                 new Vector2(xPosition, yPosition),
    63.                                                                 null,
    64.                                                                 out convertedPosition);
    65.         Debug.Log($"Pop out x position: {convertedPosition.x}, pop out y position: {convertedPosition.y} in canvas space");
    66.         if (convertedPosition.x < _leftBoundaryCanvas)
    67.         {
    68.             convertedPosition.x = _leftBoundaryCanvas;
    69.         }
    70.         if (convertedPosition.x > _rightBoundaryCanvas)
    71.         {
    72.             convertedPosition.x = _rightBoundaryCanvas;
    73.         }
    74.         if (convertedPosition.y < _bottomBoundaryCanvas)
    75.         {
    76.             convertedPosition.y = _bottomBoundaryCanvas;
    77.         }
    78.         if (convertedPosition.y > _topBoundaryCanvas)
    79.         {
    80.             convertedPosition.y = _topBoundaryCanvas;
    81.         }
    82.         Debug.Log($"Pop out x constrained: {convertedPosition.x}, pop out y constrained: {convertedPosition.y} in canvas space");
    83.         _popOutCard = Instantiate(gameObject, _canvasTransform);
    84.         _popOutCard.transform.localPosition = convertedPosition;
    85.         _popOutCard.transform.localScale = Vector3.one * _scaleFactor;
    86.         _enabled = true;
    87.     }
    88.     private void OnZoomPopOutExit()
    89.     {
    90.         Debug.Log("OnZoomPopOutExit() called");
    91.         _enabled = false;
    92.         Destroy(_popOutCard);
    93.     }
    94. }
    95.  
    96.  
     
    Last edited: Jan 29, 2023
  4. slpearlman

    slpearlman

    Joined:
    Aug 29, 2014
    Posts:
    8
    Yeah, there were issues in coordinate mapping, anchoring, and in where I constrained the coordinates. Thanks for your advice.
     
    Kurt-Dekker likes this.
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    32,371
    Oh yeah, I didn't notice your parenting there... Yes, definitely for UI stuff I always use the flavors of Instantiate<T>() that accept that final Transform parent. That seems to ensure things get setup in all cases.

    ALSO... just another random note. Not sure if these cards are actual prefabs or an in-scene template/example object, but I strongly prefer the latter for UI bits and bobs: I will put one example of each of the different "things" that can get Instantiate<T>()-ed in my UI hierarchy, call .SetActive(false) on all of them in Start(). When I need one I clone one of those examples, using the parent that it already is anchored to, and call SetActive(true)

    This approach seems to have the fewest fiddles and hassles.
     
  6. slpearlman

    slpearlman

    Joined:
    Aug 29, 2014
    Posts:
    8
    I have an editor script, which reads from a config file and generates a prefab for each card. Then I spawn instances of the prefabs, when required. I have "empty' card slots that function as parents for each card that gets instantiated. As for the zooming, for now, I'm just anchoring the the canvas, since we're just prototyping; however, if we were to use a UI setup for the "real" version of the game, given that we know exactly where cards will summon, I assume that we'd take the approach of figuring out exactly where each zoom card should be and create an inactive object there. I've been coding for a long time, but am extremely new to game dev, so I don't know the best practices yet. Hopefully this makes sense.
     
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    32,371
    How exciting! The best news is you're in for a wild ride. Hop in, hold on, there is no real "best practices," but there are certainly things to do and things to avoid.

    Unity is an absolute BEAST of a game engine capable of doing amazing things, yet also capable of being baffling. After 11 years using it I'm still learning new things about old parts of it that I had no idea about.

    I offer you these random tidbits to get your mind sparking:

    Unity Architecture: https://forum.unity.com/threads/custom-gameobject.967582/#post-6299125

    Unity Timing: https://docs.unity3d.com/Manual/ExecutionOrder.html

    Unity log output: use Debug.Log() preferentially to attaching the debugger if at all possible. Breakpointing in the debugger is awesome but utterly freezes your game AND the editor.

    And don't forget to study the scene itself while the game is running, or paused.
     
  8. slpearlman

    slpearlman

    Joined:
    Aug 29, 2014
    Posts:
    8
    Thanks for the tips :)
     
    Kurt-Dekker likes this.