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. Have a look at our Games Focus blog post series which will show what Unity is doing for all game developers – now, next year, and in the future.
    Dismiss Notice

Bug Changing the aspect ratio of a resizeable player window messes up UI "hitboxes"

Discussion in 'Immediate Mode GUI (IMGUI)' started by JWittig, Jul 27, 2022.

  1. JWittig

    JWittig

    Joined:
    Apr 5, 2020
    Posts:
    3
    Hey all,
    I discovered a bug in the unity UI system. The problem occurs when having "Resizable Window" checked in the player settings. Creating a build and resizing the application window to an specific aspect ratio (that differs from the native aspect ratio of the monitor) the UI hitboxes seem to not be in place anymore. In my case the resolution that causes the problem was 1600x840 (i displayed the resolution in a development console inside the build). Therefore one cannot hit the buttons on the right and bottom side of the window. I even tried to isolate the problem by setting up a minimal case scenario inside a new project. This scenario consists of a bunch of UI toggles on a canvas that are distributed over the right side of the canvas. Changes to the application window size seem to shift the position of the ui hitboxes even further.

    I also checked via raycast, if anything is hit on mouse down onto the toggle ui elements, but the raycastresult was empty. I could reproduce the bug with Unity Version 2021.3.4 and 2021.3.5 but not with Version 2021.2.16.

    Closely related to: https://forum.unity.com/threads/resizable-window-event-system-not-resizing-with-window.295680/

    I also posted a bug report a month ago with the minimal project attached to it. But i didnt receive any responses so far (IN-8595).

    Have anyone experienced the same issue and knows a fix or a workaround for this?
    Kind regards,
    Joel
     
    TheShadowKnight13 likes this.
  2. The6thCelestial

    The6thCelestial

    Joined:
    Feb 17, 2021
    Posts:
    1
    Thank you for suggesting reverting to a non-LTS version, I've had this exact issue, and ever since 2021 had its LTS release, there have been lots of problems from crashes to various things not working as before.

    As for giving more information - I've been using a scale size of 1920x1080 (and 0.5 for both width/height) for all resizable elements.
    • This issue is present on the latest versions of both 2020.3.37 and 2021.3.7 LTS versions.
    • This issue only exists in builds, never in the editor (Windows if that matters).
    • This bug also only seems to reproduce on my end if the UI elements are loaded through new scenes. I'm doing a sort of mixed UI where parts of it are on the main scene, while other parts are separate scenes / canvases. Their contents are made visible when needed.
    • If the screen is vertically large, but not horizontally, then UI elements seem to still be correctly clickable. However, the other way around, at some point all UI elements actual clickable area starts to float away. Weirdly though, if I horizontally pull it even longer so that the game covers 2 screens, then the UI elements start being correctly clickable again.
    So, if you want a workaround for now, maybe try to have all UI elements on the main scene and one canvas?
     
    TheShadowKnight13 likes this.
  3. Numasun

    Numasun

    Joined:
    Nov 27, 2014
    Posts:
    2
    I also encountered this bug. Long searched for what the problem is and how to solve it. Came to the conclusion that the problem in Unity Engine.

    Present in Unity versions 2022.1.11f1, 2021.3.6f1, 2021.2.17f1.
    How to reproduce:
    1. Create a project in 2021.3.6f1.
    2. In Project Settings > Player > Resolution and Presentation check "Resizable Window"
    3. Add a Canvas to the scene (don't change anything)
    4. Add a button to the Canvas
    5. Stretch the button to full screen and set Anchors to stretch
    upload_2022-8-2_14-0-41.png
    6. Build Windows
    7. Run the build, stretch the window to a 3:1 ratio and move the window to update visual problems
    8. The right-top side of the button stops responding to the mouse. Green marked area where the button is responsive
    upload_2022-8-2_14-0-9.png

    There is no bug in Unity 2019.4.28f1 LTS version.
    • Solved the problem by using Raycaster from Unity 2019.4.28f1. Attached to the post for the lazy ;)
    I think the problem is with tracking multiple screens in the method of determining mouse position on screens.
    GraphicRaycaster Unity 2021.3.6f1:
    Code (CSharp):
    1. var eventPosition = MultipleDisplayUtilities.RelativeMouseAtScaled(eventData.position);
    GraphicRaycaster Unity 2019.4.28f1:
    Code (CSharp):
    1. var eventPosition = Display.RelativeMouseAt(eventData.position);
     

    Attached Files:

  4. chrpetry

    chrpetry

    Joined:
    Mar 7, 2018
    Posts:
    47
    I also now stumbled upon this bug.
    Don't actually want to fiddle with Unity code or write a custom GraphicsRaycaster because of this.
    Is there already a bug opened in public bug tracker from Unity where I can vote on?
     
  5. chrpetry

    chrpetry

    Joined:
    Mar 7, 2018
    Posts:
    47
  6. hmok

    hmok

    Joined:
    Jan 12, 2016
    Posts:
    1
    You can use the code to fixed this bug.Replace <GraphicRaycaster> component on Canvas by <GraphicRaycasterBugFixedByHm>.


    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Text;
    4. using UnityEngine;
    5. using UnityEngine.EventSystems;
    6. using UnityEngine.Serialization;
    7. using UnityEngine.UI;
    8.  
    9. /// <summary>
    10. /// 修复UnityUGui中Canvas画布在Windows平台宽高比过长(横屏拉很长)导致UI的最右边一部分无法被点击的Buv,现发现与2021.3.x版本 by:黄敏 2022.11.9
    11. /// </summary>
    12. public class GraphicRaycasterBugFixedByHm : UnityEngine.UI.GraphicRaycaster
    13. {
    14.     [NonSerialized] private List<Graphic> m_RaycastResults = new List<Graphic>();
    15.     private Canvas m_Canvas;
    16.     private Canvas canvas
    17.     {
    18.         get
    19.         {
    20.             if (m_Canvas != null)
    21.                 return m_Canvas;
    22.  
    23.             m_Canvas = GetComponent<Canvas>();
    24.             return m_Canvas;
    25.         }
    26.     }
    27.     /// <summary>
    28.     /// Perform the raycast against the list of graphics associated with the Canvas.
    29.     /// </summary>
    30.     /// <param name="eventData">Current event data</param>
    31.     /// <param name="resultAppendList">List of hit objects to append new results to.</param>
    32.     public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList)
    33.     {
    34.         if (canvas == null)
    35.             return;
    36.  
    37.         var canvasGraphics = GraphicRegistry.GetRaycastableGraphicsForCanvas(canvas);
    38.         if (canvasGraphics == null || canvasGraphics.Count == 0)
    39.             return;
    40.  
    41.         int displayIndex;
    42.         var currentEventCamera = eventCamera; // Property can call Camera.main, so cache the reference
    43.  
    44.         if (canvas.renderMode == RenderMode.ScreenSpaceOverlay || currentEventCamera == null)
    45.             displayIndex = canvas.targetDisplay;
    46.         else
    47.             displayIndex = currentEventCamera.targetDisplay;
    48.  
    49.         var eventPosition = Display.RelativeMouseAt(eventData.position);//MultipleDisplayUtilities.RelativeMouseAtScaled(eventData.position);
    50.         if (eventPosition != Vector3.zero)
    51.         {
    52.             // We support multiple display and display identification based on event position.
    53.  
    54.             int eventDisplayIndex = (int)eventPosition.z;
    55.  
    56.             // Discard events that are not part of this display so the user does not interact with multiple displays at once.
    57.             if (eventDisplayIndex != displayIndex)
    58.                 return;
    59.         }
    60.         else
    61.         {
    62.             // The multiple display system is not supported on all platforms, when it is not supported the returned position
    63.             // will be all zeros so when the returned index is 0 we will default to the event data to be safe.
    64.             eventPosition = eventData.position;
    65.  
    66. #if UNITY_EDITOR
    67.             if (Display.activeEditorGameViewTarget != displayIndex)
    68.                 return;
    69.             eventPosition.z = Display.activeEditorGameViewTarget;
    70. #endif
    71.  
    72.             // We dont really know in which display the event occured. We will process the event assuming it occured in our display.
    73.         }
    74.  
    75.         // Convert to view space
    76.         Vector2 pos;
    77.         if (currentEventCamera == null)
    78.         {
    79.             // Multiple display support only when not the main display. For display 0 the reported
    80.             // resolution is always the desktops resolution since its part of the display API,
    81.             // so we use the standard none multiple display method. (case 741751)
    82.             float w = Screen.width;
    83.             float h = Screen.height;
    84.             if (displayIndex > 0 && displayIndex < Display.displays.Length)
    85.             {
    86.                 w = Display.displays[displayIndex].systemWidth;
    87.                 h = Display.displays[displayIndex].systemHeight;
    88.             }
    89.             pos = new Vector2(eventPosition.x / w, eventPosition.y / h);
    90.         }
    91.         else
    92.             pos = currentEventCamera.ScreenToViewportPoint(eventPosition);
    93.  
    94.         // If it's outside the camera's viewport, do nothing
    95.         if (pos.x < 0f || pos.x > 1f || pos.y < 0f || pos.y > 1f)
    96.             return;
    97.  
    98.         float hitDistance = float.MaxValue;
    99.  
    100.         Ray ray = new Ray();
    101.  
    102.         if (currentEventCamera != null)
    103.             ray = currentEventCamera.ScreenPointToRay(eventPosition);
    104.  
    105.         if (canvas.renderMode != RenderMode.ScreenSpaceOverlay && blockingObjects != BlockingObjects.None)
    106.         {
    107.             float distanceToClipPlane = 100.0f;
    108.  
    109.             if (currentEventCamera != null)
    110.             {
    111.                 float projectionDirection = ray.direction.z;
    112.                 distanceToClipPlane = Mathf.Approximately(0.0f, projectionDirection)
    113.                     ? Mathf.Infinity
    114.                     : Mathf.Abs((currentEventCamera.farClipPlane - currentEventCamera.nearClipPlane) / projectionDirection);
    115.             }
    116. #if PACKAGE_PHYSICS
    117.                 if (blockingObjects == BlockingObjects.ThreeD || blockingObjects == BlockingObjects.All)
    118.                 {
    119.                     if (ReflectionMethodsCache.Singleton.raycast3D != null)
    120.                     {
    121.                         var hits = ReflectionMethodsCache.Singleton.raycast3DAll(ray, distanceToClipPlane, (int)m_BlockingMask);
    122.                         if (hits.Length > 0)
    123.                             hitDistance = hits[0].distance;
    124.                     }
    125.                 }
    126. #endif
    127. #if PACKAGE_PHYSICS2D
    128.                 if (blockingObjects == BlockingObjects.TwoD || blockingObjects == BlockingObjects.All)
    129.                 {
    130.                     if (ReflectionMethodsCache.Singleton.raycast2D != null)
    131.                     {
    132.                         var hits = ReflectionMethodsCache.Singleton.getRayIntersectionAll(ray, distanceToClipPlane, (int)m_BlockingMask);
    133.                         if (hits.Length > 0)
    134.                             hitDistance = hits[0].distance;
    135.                     }
    136.                 }
    137. #endif
    138.         }
    139.  
    140.         m_RaycastResults.Clear();
    141.  
    142.         Raycast(canvas, currentEventCamera, eventPosition, canvasGraphics, m_RaycastResults);
    143.  
    144.         int totalCount = m_RaycastResults.Count;
    145.         for (var index = 0; index < totalCount; index++)
    146.         {
    147.             var go = m_RaycastResults[index].gameObject;
    148.             bool appendGraphic = true;
    149.  
    150.             if (ignoreReversedGraphics)
    151.             {
    152.                 if (currentEventCamera == null)
    153.                 {
    154.                     // If we dont have a camera we know that we should always be facing forward
    155.                     var dir = go.transform.rotation * Vector3.forward;
    156.                     appendGraphic = Vector3.Dot(Vector3.forward, dir) > 0;
    157.                 }
    158.                 else
    159.                 {
    160.                     // If we have a camera compare the direction against the cameras forward.
    161.                     var cameraForward = currentEventCamera.transform.rotation * Vector3.forward * currentEventCamera.nearClipPlane;
    162.                     appendGraphic = Vector3.Dot(go.transform.position - currentEventCamera.transform.position - cameraForward, go.transform.forward) >= 0;
    163.                 }
    164.             }
    165.  
    166.             if (appendGraphic)
    167.             {
    168.                 float distance = 0;
    169.                 Transform trans = go.transform;
    170.                 Vector3 transForward = trans.forward;
    171.  
    172.                 if (currentEventCamera == null || canvas.renderMode == RenderMode.ScreenSpaceOverlay)
    173.                     distance = 0;
    174.                 else
    175.                 {
    176.                     // http://geomalgorithms.com/a06-_intersect-2.html
    177.                     distance = (Vector3.Dot(transForward, trans.position - ray.origin) / Vector3.Dot(transForward, ray.direction));
    178.  
    179.                     // Check to see if the go is behind the camera.
    180.                     if (distance < 0)
    181.                         continue;
    182.                 }
    183.  
    184.                 if (distance >= hitDistance)
    185.                     continue;
    186.  
    187.                 var castResult = new RaycastResult
    188.                 {
    189.                     gameObject = go,
    190.                     module = this,
    191.                     distance = distance,
    192.                     screenPosition = eventPosition,
    193.                     displayIndex = displayIndex,
    194.                     index = resultAppendList.Count,
    195.                     depth = m_RaycastResults[index].depth,
    196.                     sortingLayer = canvas.sortingLayerID,
    197.                     sortingOrder = canvas.sortingOrder,
    198.                     worldPosition = ray.origin + ray.direction * distance,
    199.                     worldNormal = -transForward
    200.                 };
    201.                 resultAppendList.Add(castResult);
    202.             }
    203.         }
    204.     }
    205.     /// <summary>
    206.     /// Perform a raycast into the screen and collect all graphics underneath it.
    207.     /// </summary>
    208.     [NonSerialized] static readonly List<Graphic> s_SortedGraphics = new List<Graphic>();
    209.     private static void Raycast(Canvas canvas, Camera eventCamera, Vector2 pointerPosition, IList<Graphic> foundGraphics, List<Graphic> results)
    210.     {
    211.         // Necessary for the event system
    212.         int totalCount = foundGraphics.Count;
    213.         for (int i = 0; i < totalCount; ++i)
    214.         {
    215.             Graphic graphic = foundGraphics[i];
    216.  
    217.             // -1 means it hasn't been processed by the canvas, which means it isn't actually drawn
    218.             if (!graphic.raycastTarget || graphic.canvasRenderer.cull || graphic.depth == -1)
    219.                 continue;
    220.  
    221.             if (!RectTransformUtility.RectangleContainsScreenPoint(graphic.rectTransform, pointerPosition, eventCamera, graphic.raycastPadding))
    222.                 continue;
    223.  
    224.             if (eventCamera != null && eventCamera.WorldToScreenPoint(graphic.rectTransform.position).z > eventCamera.farClipPlane)
    225.                 continue;
    226.  
    227.             if (graphic.Raycast(pointerPosition, eventCamera))
    228.             {
    229.                 s_SortedGraphics.Add(graphic);
    230.             }
    231.         }
    232.  
    233.         s_SortedGraphics.Sort((g1, g2) => g2.depth.CompareTo(g1.depth));
    234.         totalCount = s_SortedGraphics.Count;
    235.         for (int i = 0; i < totalCount; ++i)
    236.             results.Add(s_SortedGraphics[i]);
    237.  
    238.         s_SortedGraphics.Clear();
    239.     }
    240. }
     
unityunity