Search Unity

Unity UI Keep UI objects inside screen.

Discussion in 'UGUI & TextMesh Pro' started by esteban16108, Mar 27, 2018.

  1. esteban16108

    esteban16108

    Joined:
    Jan 23, 2014
    Posts:
    159
    Hi,

    I'm working on this game where words fall from the screen and you have to collect them clicking on them.

    The words in my project are actually buttons, and when spawning buttons near the border of the screen some of them have a part outside the screen.

    What I wanted to do is correct the position and make them to be fully inside the screen. What I thought was calculating the width of the button and then change the position based on (width/2) depending on which side of the screen it is. The problem is that the width is in pixels and whatever I try I can't seem to do this right.

    Any idea or how to implement it better?

    Thanks.

    EDIT: working in Screen Space - Camera
     
    Last edited: Mar 27, 2018
  2. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    This is one option - works with screen space overlay and the proper pivot/anchors. :)

    Code (csharp):
    1. RectTransform mrect = GetComponent<RectTransform>();
    2. Vector2 apos = mrect.anchoredPosition;
    3. float xpos = apos.x;
    4. xpos = Mathf.Clamp(xpos, 0, Screen.width - mrect.sizeDelta.x);
    5. apos.x = xpos;
    6. mrect.anchoredPosition = apos;
     
    oAzuehT likes this.
  3. esteban16108

    esteban16108

    Joined:
    Jan 23, 2014
    Posts:
    159
    Sorry, Forgot to mention it's Screen Space Camera.
     
  4. esteban16108

    esteban16108

    Joined:
    Jan 23, 2014
    Posts:
    159

    So that above is what I'm trying to achieve.

    I have the following for left side right now.

    Code (CSharp):
    1.  
    2.             float ww = wordRT.rect.width;
    3.          
    4.             Vector2 wScreenPos = Camera.main.WorldToScreenPoint(wordRT.position);
    5.             Debug.Log("*** Word Screen Position: " + Camera.main.WorldToScreenPoint(wordRT.position));
    6.  
    7.             wScreenPos.x = wScreenPos.x + ((ww/2f) - wScreenPos.x);
    8.             Debug.Log("*** word new Screen pos: " + wScreenPos);
    9.             Vector3 outV = new Vector3();
    10.             RectTransformUtility.ScreenPointToWorldPointInRectangle(CanvasRT, wScreenPos, Camera.current, out outV);
    11. //            Vector2 wWorldPos = Camera.main.ScreenToWorldPoint(wScreenPos);
    12.             Vector2 wWorldPos = (Vector2) outV;
    13.             wordRT.position = wWorldPos;
     
  5. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Just tested the code (from my post) with screen space canvas, and it's working the same (working well*) . :)
     
  6. esteban16108

    esteban16108

    Joined:
    Jan 23, 2014
    Posts:
    159
    @methos5k Sorry, it works but the button gets to the absolute middle... what I want to do is shown it the pic above.

    I think I got it ... have to test it and will post it.

    Thanks.
     
  7. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    For sure, that is the anchors/pivot setup :) (well, for the code to work exactly as I wrote it.)
    Or it's some math to work out the numbers of where it would be with the current anchor/pivot.
     
  8. esteban16108

    esteban16108

    Joined:
    Jan 23, 2014
    Posts:
    159
    Ended with this:

    Code (CSharp):
    1.  
    2.            RectTransform wordRT = word.GetComponent<RectTransform>();
    3.  
    4.             Vector2 wordScreenPosition = Camera.main.WorldToScreenPoint(wordRT.position);
    5.             float wordWidth = wordRT.rect.width;
    6.             float wordLeftPos = wordScreenPosition.x - (wordWidth / 2f);
    7.             float wordRightPos = wordScreenPosition.x + (wordWidth / 2f);
    8.  
    9.             if (wordLeftPos < 0)
    10.             {
    11.                 wordScreenPosition.x = wordScreenPosition.x + ((wordWidth / 2f) - wordScreenPosition.x);
    12.  
    13.                 Vector2 wordWorldPos = Camera.main.ScreenToWorldPoint(wordScreenPosition);
    14.                 wordRT.position = wordWorldPos;
    15.             }
    16.             else if (wordRightPos > Camera.main.pixelWidth)
    17.             {
    18.                 float deltaPosX = Camera.main.pixelWidth - wordScreenPosition.x;
    19.                 float diff = (wordWidth / 2f) - deltaPosX;
    20.                 wordScreenPosition.x = wordScreenPosition.x - diff;
    21.                
    22.                 Vector2 wordWorldPos = Camera.main.ScreenToWorldPoint(wordScreenPosition);
    23.                 wordRT.position = wordWorldPos;
    24.             }
    :p
     
    ihsan07 and Mauri like this.
  9. y0u553ef

    y0u553ef

    Joined:
    May 2, 2017
    Posts:
    27
    I did try you solution but it only works on fixed resolution when I change the screen resolution (which change the canvas resolution) it won't work , cos we are actually working with pixels depending on the screen resolution of the player (I am using Screen space-Camera canvas and Scale with Screen Size canas scaler)
     
  10. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Yes, it seems that I did not test enough.
    Screen space camera is something I'd like to get better at working with someday. I never use it. :)

    Edit: You can also reference the RectTransform of the canvas, and use its rect 'width' as your "Screen width" (so to speak). I worked out a few simple tests using this value to clamp the anchored position..
     
    Last edited: Mar 30, 2018
  11. awesomedata

    awesomedata

    Joined:
    Oct 8, 2014
    Posts:
    1,419
    Been having trouble with precise positioning of UI rects like this lately.
    Did anyone ever come up with a solution for this?
     
  12. idealconceptz

    idealconceptz

    Joined:
    Aug 16, 2018
    Posts:
    3
    ok, after a bit of playing around, figured out the best way for mine was this. It works on all resolutions and devices tested.

    Code (CSharp):
    1. Vector3 KeepFullyOnScreen( GameObject panel, Vector3 newPos)
    2. {
    3.     RectTransform rect = panel.GetComponent<RectTransform>();
    4.     RectTransform CanvasRect = canvas.GetComponent<RectTransform>();
    5.  
    6.     float minX = (CanvasRect.sizeDelta.x - rect.sizeDelta.x) * -0.5f;
    7.     float maxX = (CanvasRect.sizeDelta.x - rect.sizeDelta.x) * 0.5f;
    8.     float minY = (CanvasRect.sizeDelta.y - rect.sizeDelta.y) * -0.5f;
    9.     float maxY = (CanvasRect.sizeDelta.y - rect.sizeDelta.y) * 0.5f;
    10.  
    11.     newPos.x = Mathf.Clamp(newPos.x, minX, maxX);
    12.     newPos.y = Mathf.Clamp(newPos.y, minY, maxY);
    13.  
    14.     return newPos;
    15. }
     
  13. Berlm

    Berlm

    Joined:
    Dec 27, 2019
    Posts:
    4
    works!
    for those who cant understand, the panel is the object that has to be kept inside, and the newpos is the position of the object that is supposed to be kept inside. I used anchoredposition, so I just changed everything from vector3 to vector2
     
    RunninglVlan likes this.
  14. RunninglVlan

    RunninglVlan

    Joined:
    Nov 6, 2018
    Posts:
    182
    Works, for mine had to move anchor and pivot to middle center (.5, .5)!

    Update: Here's a solution that doesn't depend on anchor/pivot values:
    Code (CSharp):
    1. // RectTransform canvas, panel;
    2. var sizeDelta = canvas.sizeDelta - panel.sizeDelta;
    3. var panelPivot = panel.pivot;
    4. var position = panel.anchoredPosition;
    5. position.x = Mathf.Clamp(position.x, -sizeDelta.x * panelPivot.x, sizeDelta.x * (1 - panelPivot.x));
    6. position.y = Mathf.Clamp(position.y, -sizeDelta.y * panelPivot.y, sizeDelta.y * (1 - panelPivot.y));
    7. panel.anchoredPosition = position;
     
    Last edited: Oct 19, 2020
  15. RunninglVlan

    RunninglVlan

    Joined:
    Nov 6, 2018
    Posts:
    182
    We added zooming to one UI so my previous solution has stopped working - movable could now be bigger than the container.
    This one shouldn't depend on anchors/pivots (and scale):

    Code (CSharp):
    1. // RectTransform container, movable;
    2. // Vector3[] cornersCache = new Vector3[4];
    3. container.GetWorldCorners(cornersCache);
    4. // BL = Bottom Left, TR = Top Right (corners)
    5. Vector3 containerBL = cornersCache[0], containerTR = cornersCache[2];
    6. movable.GetWorldCorners(cornersCache);
    7. Vector3 movableBL = cornersCache[0], movableTR = cornersCache[2];
    8.  
    9. var position = movable.position;
    10. Vector3 deltaBL = position - movableBL, deltaTR = movableTR - position;
    11. position.x = Mathf.Clamp(position.x, containerBL.x + deltaBL.x, containerTR.x - deltaTR.x);
    12. position.y = Mathf.Clamp(position.y, containerBL.y + deltaBL.y, containerTR.y - deltaTR.y);
    13. movable.position = position;
    And this one should keep movable "inside" the container even if it's bigger than the container =)
    Code (CSharp):
    1. // RectTransform container, movable;
    2. // Vector3[] cornersCache = new Vector3[4];
    3. container.GetWorldCorners(cornersCache);
    4. // BL = Bottom Left, TR = Top Right (corners)
    5. Vector3 containerBL = cornersCache[0], containerTR = cornersCache[2];
    6. var containerSize = containerTR - containerBL; // NEW
    7. movable.GetWorldCorners(cornersCache);
    8. Vector3 movableBL = cornersCache[0], movableTR = cornersCache[2];
    9. var movableSize = movableTR - movableBL; // NEW
    10.  
    11. var position = movable.position;
    12. Vector3 deltaBL = position - movableBL, deltaTR = movableTR - position;
    13. position.x = movableSize.x < containerSize.x // NEW
    14.   ? Mathf.Clamp(position.x, containerBL.x + deltaBL.x, containerTR.x - deltaTR.x)
    15.   : Mathf.Clamp(position.x, containerTR.x - deltaTR.x, containerBL.x + deltaBL.x); // NEW
    16. position.y = movableSize.y < containerSize.y // NEW
    17.   ? Mathf.Clamp(position.y, containerBL.y + deltaBL.y, containerTR.y - deltaTR.y)
    18.   : Mathf.Clamp(position.y, containerTR.y - deltaTR.y, containerBL.y + deltaBL.y); // NEW
    19. movable.position = position;
     
    FeastSC2, Zurdiche, oAzuehT and 8 others like this.
  16. Jackoulalaboucanou

    Jackoulalaboucanou

    Joined:
    Jun 19, 2018
    Posts:
    2
    Thank you RunningIVIan

    Was very useful for my project! I appreciate your script
     
    RunninglVlan likes this.
  17. brummer

    brummer

    Joined:
    Jul 3, 2013
    Posts:
    31
    Hey, Runningman!

    The script seems to work, but once narrowing the screen, after widening it back out, the elements seem to get stuck where it was pushed back to. Is there a way to fix this?
     
  18. SeerSucker69

    SeerSucker69

    Joined:
    Mar 6, 2021
    Posts:
    68
    Ok after may hours of searching I ended up with the following method that pops up the UI element near the mouse but keeps it on the screen.

    Pass it your mouse position (Input.mousePosition), the popup windows RectTransform, and the RectTransform of the canvas your panel is on.

    Code (CSharp):
    1. void ClampToWindow( Vector3 MyMouse,  RectTransform panelRectTransform, RectTransform parentRectTransform)
    2.     {
    3.  
    4.         panelRectTransform.transform.position  = MyMouse;
    5.  
    6.         Vector3 pos = panelRectTransform.localPosition;
    7.  
    8.         Vector3 minPosition = parentRectTransform.rect.min - panelRectTransform.rect.min;
    9.         Vector3 maxPosition = parentRectTransform.rect.max - panelRectTransform.rect.max;
    10.  
    11.         pos.x = Mathf.Clamp(panelRectTransform.localPosition.x, minPosition.x, maxPosition.x);
    12.         pos.y = Mathf.Clamp(panelRectTransform.localPosition.y, minPosition.y, maxPosition.y);
    13.  
    14.         panelRectTransform.localPosition = pos;
    15.     }
     
  19. alexbarra

    alexbarra

    Joined:
    Feb 2, 2020
    Posts:
    2
    Hello there, I had the same issue and found a cool solution. I've posted a thread with my solution here. Check it and tell me if it has been useful.
     
    SeerSucker69 likes this.
  20. criwe

    criwe

    Joined:
    Apr 16, 2013
    Posts:
    5
    I needed this for my item tooltip, it works for anything regardless of pivot!

    Code (CSharp):
    1.  
    2. void KeepFullyOnScreen()
    3.     {
    4.         var position = rect.anchoredPosition;
    5.         float minX = (GameDataManager.singleton.canvasRect.sizeDelta.x * -0.5f) - ((rect.sizeDelta.x * rect.pivot.x) * -1f);
    6.         float maxX = (GameDataManager.singleton.canvasRect.sizeDelta.x * 0.5f) - (rect.sizeDelta.x * (1 - rect.pivot.x));
    7.         float minY = (GameDataManager.singleton.canvasRect.sizeDelta.y * -0.5f) - ((rect.sizeDelta.y * rect.pivot.y) * -1f);
    8.         float maxY = (GameDataManager.singleton.canvasRect.sizeDelta.y * 0.5f) - (rect.sizeDelta.y * (1-rect.pivot.y));
    9.  
    10.         position.x = Mathf.Clamp(position.x, minX, maxX);
    11.         position.y = Mathf.Clamp(position.y, minY, maxY);
    12.  
    13.         rect.anchoredPosition = position;
    14.     }
    15.  
     
  21. juanimumbach

    juanimumbach

    Joined:
    Nov 17, 2021
    Posts:
    1
    I Improved the code in the comment above mine.
    With my code it works regardless of pivot point but also regardless anchor positions (e.g. an element anchored to the top right corner of the canvas).
    Anyway thanks to @Chrizp , without his code probably I would have taken much more time to reach to this solution.

    Code (CSharp):
    1. void KeepFullyOnScreen()
    2.     {
    3.         RectTransform canvas = transform.parent.GetComponent<RectTransform>();
    4.         RectTransform rect = GetComponent<RectTransform>();
    5.  
    6.         Vector2 anchorOffset = canvas.sizeDelta * (rect.anchorMin - Vector2.one / 2);
    7.      
    8.         Vector2 maxPivotOffset = rect.sizeDelta * (rect.pivot - (Vector2.one / 2) * 2);
    9.         Vector2 minPivotOffset = rect.sizeDelta * ((Vector2.one / 2) * 2 - rect.pivot);
    10.  
    11.         Vector2 position = rect.anchoredPosition;
    12.  
    13.         float minX = (canvas.sizeDelta.x) * -0.5f - anchorOffset.x - minPivotOffset.x + rect.sizeDelta.x;
    14.         float maxX = (canvas.sizeDelta.x) * 0.5f  - anchorOffset.x + maxPivotOffset.x ;
    15.         float minY = (canvas.sizeDelta.y) * -0.5f - anchorOffset.y - minPivotOffset.y + rect.sizeDelta.y;
    16.         float maxY = (canvas.sizeDelta.y) * 0.5f  - anchorOffset.y + maxPivotOffset.y ;
    17.  
    18.         position.x = Mathf.Clamp(position.x, minX, maxX);
    19.         position.y = Mathf.Clamp(position.y, minY, maxY);
    20.  
    21.         rect.anchoredPosition = position;
    22.     }
     
  22. sarahnorthway

    sarahnorthway

    Joined:
    Jul 16, 2015
    Posts:
    78
    This works beautifully. I adjusted it slightly to work with dynamic menu scaling:

    Code (CSharp):
    1. void KeepFullyOnScreen()
    2.     {
    3.         RectTransform canvas = transform.parent.GetComponent<RectTransform>();
    4.         RectTransform rect = GetComponent<RectTransform>();
    5.  
    6.         Vector2 sizeDelta = rect.sizeDelta * transform.localScale;
    7.         Vector2 anchorOffset = canvas.sizeDelta * (rect.anchorMin - Vector2.one / 2);
    8.    
    9.         Vector2 maxPivotOffset = sizeDelta * (rect.pivot - (Vector2.one / 2) * 2);
    10.         Vector2 minPivotOffset = sizeDelta * ((Vector2.one / 2) * 2 - rect.pivot);
    11.  
    12.         float minX = (canvas.sizeDelta.x) * -0.5f - anchorOffset.x - minPivotOffset.x + sizeDelta.x;
    13.         float maxX = (canvas.sizeDelta.x) * 0.5f  - anchorOffset.x + maxPivotOffset.x;
    14.         float minY = (canvas.sizeDelta.y) * -0.5f - anchorOffset.y - minPivotOffset.y + sizeDelta.y;
    15.         float maxY = (canvas.sizeDelta.y) * 0.5f  - anchorOffset.y + maxPivotOffset.y;
    16.  
    17.         position.x = Mathf.Clamp(position.x, minX, maxX);
    18.         position.y = Mathf.Clamp(position.y, minY, maxY);
    19.  
    20.         rect.anchoredPosition = position;
    21.     }
     
  23. unity_0D1DB9A54EFA29DD5595

    unity_0D1DB9A54EFA29DD5595

    Joined:
    Aug 15, 2023
    Posts:
    1
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    public class PlayerMovement : MonoBehaviour
    {
    [SerializeField] GameObject player;
    [SerializeField] float speed = 20f;
    private RectTransform canvasRectTransform;
    // Start is called before the first frame update
    void Start()
    {
    // Get a reference to the canvas
    canvasRectTransform = GetComponentInParent<Canvas>().GetComponent<RectTransform>();
    }
    // Update is called once per frame
    void Update()
    {
    // Calculate the canvas size
    Vector2 canvasSize = canvasRectTransform.sizeDelta;
    // Calculate the center position of the canvas
    Vector2 centerPosition = new Vector2(canvasSize.x / 2, canvasSize.y / 2);
    // Calculate the direction towards the center
    Vector2 directionToCenter = (centerPosition - (Vector2)player.transform.position).normalized;
    // Calculate the target position towards the center
    Vector2 targetPosition = (Vector2)player.transform.position + directionToCenter;
    // Move towards the target position
    transform.position = Vector2.MoveTowards(player.transform.position, targetPosition, speed * Time.deltaTime);
    // Rotate towards the player position
    player.transform.up = targetPosition - (Vector2)player.transform.position;
    }
    }
    in this code i want angle of game object is center and i want game object move to end screen of canvas in unity 2 d game
     
  24. CreatAWorld

    CreatAWorld

    Joined:
    Jul 1, 2019
    Posts:
    7
    I have a method that guarantees that the UI is in the screen no matter how it is set, whether the UI has a parent object or not, and no matter how the RenderMode of the Canvas is set
    Code (CSharp):
    1.  
    2.     public static void UIMustInScreen(RectTransform target, Canvas canvas = null) {
    3.         if (canvas == null) {
    4.             canvas = target.FindCanvas();
    5.         }
    6.        
    7.         RectTransform canvasRect = canvas.transform as RectTransform;
    8.         RectTransform parentRect = target.parent.GetComponent<RectTransform>();
    9.         Camera        cam        = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera;
    10.         Vector2       screenPos  = RectTransformUtility.WorldToScreenPoint(cam, target.position);
    11.  
    12.         float minX = target.pivot.x * target.rect.size.x;
    13.         float maxX = canvasRect.rect.size.x - (1 - target.pivot.x) * target.rect.size.x;
    14.         float minY = target.pivot.y * target.rect.size.y;
    15.         float maxY = canvasRect.rect.size.y - (1 - target.pivot.y) * target.rect.size.y;
    16.        
    17.         screenPos.x = Mathf.Clamp(screenPos.x, minX, maxX);
    18.         screenPos.y = Mathf.Clamp(screenPos.y, minY, maxY);
    19.  
    20.         RectTransformUtility.ScreenPointToLocalPointInRectangle(parentRect, screenPos, cam, out Vector2 anchoredPos);
    21.         target.localPosition = anchoredPos;
    22.     }
     
    F4bs, erenaydin and JevinLevin_ like this.