Search Unity

Question How Can I Make a Child Object Stay Within the Player's View Inside a Grandparent's Mask?

Discussion in 'UGUI & TextMesh Pro' started by sliphtrex, Jan 22, 2024.

  1. sliphtrex

    sliphtrex

    Joined:
    Apr 18, 2015
    Posts:
    1
    Hello everyone! First time poster. I'm working on a murder mystery game and trying to establish a timeline mechanic in which the player drops info bubbles into place like puzzle pieces. I have an empty parent object TimelineHolder inside of a canvas. The TimelineHolder contains a Rect Mask 2D. Inside that is an empty object called Scroller which I'm using to zoom in and scroll around the timeline. Inside this I have the timeline image itself along with the individual image objects I'm using for the puzzle pieces. Right now I'm able to click and drag the images across the screen and I even have them clamping their position at the edge of the timeline image regardless of zoom size. My issue now is that I want to make sure the images don't go outside of the TimelineHolder's Rect Mask 2D.

    Here's the script I'm using for the Scroller object. Aside from needing to adjust some variables for the edge panning it works fine.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine.EventSystems;
    4. using UnityEngine;
    5.  
    6. public class MaskZoom : MonoBehaviour, IScrollHandler
    7. {
    8.     //zoom vars
    9.     public float zoomSpeed = .1f;
    10.     public float maxZoom;
    11.  
    12.     public Vector3 startSize;
    13.     public Vector3 desiredScale;
    14.  
    15.     //move vars
    16.     public Canvas canvas;
    17.     public float edgesize = 20f;
    18.     public float leftedge,rightedge,topedge,bottomedge;
    19.  
    20.     private float startPosX, startPosY;
    21.  
    22.     private void Awake()
    23.     {
    24.         startSize = transform.localScale;
    25.         desiredScale = startSize;
    26.     }
    27.  
    28.     public void Update()
    29.     {
    30.         if(Input.GetMouseButtonDown(0) && !TimeLineManager.tlm.itemHeld)
    31.         {
    32.             Vector3 mousePos = Input.mousePosition / canvas.scaleFactor;
    33.             startPosX = mousePos.x - 960f - transform.position.x;
    34.             startPosY = mousePos.y - 540f - transform.position.y;
    35.         }
    36.         else if(Input.GetMouseButton(0))
    37.         {
    38.             if(!TimeLineManager.tlm.itemHeld)
    39.             {
    40.                 Vector2 mousePos = Input.mousePosition / canvas.scaleFactor;
    41.                 mousePos.x -= 960;
    42.                 mousePos.y -= 540;
    43.  
    44.                 transform.position = new Vector3(mousePos.x - startPosX, mousePos.y - startPosY,
    45.                     transform.position.z);
    46.             }
    47.             else
    48.             {
    49.                 Vector2 addon = new Vector2 (transform.localPosition.x, transform.localPosition.y);
    50.                 if(Input.mousePosition.x < leftedge)
    51.                 {
    52.                     addon.x -= (Input.mousePosition.x - leftedge) * Time.deltaTime;
    53.                 }
    54.                 else if(Input.mousePosition.x > (Screen.width / 2f))
    55.                 {
    56.                     addon.x += (Input.mousePosition.x - rightedge) * Time.deltaTime;
    57.                 }
    58.  
    59.                 if(Input.mousePosition.y > topedge)
    60.                 {
    61.                     addon.y += (Input.mousePosition.y - topedge) * Time.deltaTime;
    62.                 }
    63.                 else if(Input.mousePosition.y < bottomedge)
    64.                 {
    65.                     addon.y -= (Input.mousePosition.y - bottomedge) * Time.deltaTime;
    66.                 }
    67.                 transform.localPosition= new Vector3(addon.x, addon.y, transform.localPosition.z);
    68.             }
    69.             SnapToConstraints();
    70.         }
    71.     }
    72.  
    73.     public void OnScroll(PointerEventData eventData)
    74.     {
    75.         Vector3 delta = Vector3.one * (eventData.scrollDelta.y * zoomSpeed);
    76.         desiredScale = transform.localScale + delta;
    77.         desiredScale = ClampDesiredScale(desiredScale);
    78.  
    79.         transform.localScale = desiredScale;
    80.         SnapToConstraints();
    81.     }
    82.  
    83.     private Vector3 ClampDesiredScale(Vector3 scale)
    84.     {
    85.         scale = Vector3.Max(startSize, scale);
    86.         scale = Vector3.Min(startSize * maxZoom, scale);
    87.         return scale;
    88.     }
    89.  
    90.     private void SnapToConstraints()
    91.     {
    92.         float minXPos = (desiredScale.x - startSize.x) * -850f;
    93.         float maxXPos = (desiredScale.x - startSize.x) * 850f;
    94.         float minYPos = (desiredScale.y - startSize.y) * -500f;
    95.         float maxYPos = (desiredScale.y - startSize.y) * 500f;
    96.         float newX = Mathf.Clamp(transform.localPosition.x, minXPos, maxXPos);
    97.         float newY = Mathf.Clamp(transform.localPosition.y, minYPos, maxYPos);
    98.  
    99.         transform.localPosition = new Vector3(newX, newY, transform.localPosition.z);
    100.     }
    101. }
    Meanwhile, this is the script I'm using for the Click and Drag functionality of the puzzle pieces. I just managed to get the pieces snapping to the edge of the Timeline, but they are still able to move off the screen or outside the Rect Mask 2D when dragging them. Not to mention when this happens they get offset from the mouse position because either the mouse or the piece moves outside the viewport window. I've mainly been focusing on the SnapToConstraints function, but I might create another method to set the "don't go out of view" constraints separate.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine.EventSystems;
    4. using UnityEngine;
    5.  
    6. public class ClickNDrag : MonoBehaviour, IPointerDownHandler
    7. {
    8.     public Canvas canvas;
    9.     public MaskZoom zoom;
    10.     public float marginX, marginY;
    11.     public int myNum;
    12.  
    13.     private float startPosX;
    14.     private float startPosY;
    15.     private bool isHeld = false;
    16.     private bool isFixed = false;
    17.  
    18.     void Update()
    19.     {
    20.         //checks if we've reached our target
    21.         if (Input.GetMouseButtonUp(0) && isHeld)
    22.         {
    23.             isHeld = false;
    24.             TimeLineManager.tlm.itemHeld = false;
    25.             Vector2 myPos = new Vector2(transform.localPosition.x, transform.localPosition.y);
    26.             if (TimeLineManager.tlm.CheckLockIn(myPos, myNum) != Vector2.zero)
    27.             { transform.localPosition = TimeLineManager.tlm.CheckLockIn(myPos, myNum); }
    28.             myPos = new Vector2(transform.localPosition.x, transform.localPosition.y);
    29.             isFixed = TimeLineManager.tlm.CheckAgain(myPos, myNum);
    30.         }
    31.  
    32.         //updates the position
    33.         if (!isFixed && isHeld)
    34.         {
    35.             transform.localPosition = new Vector3(MouseToPixels().x + startPosX,
    36.                 MouseToPixels().y + startPosY, transform.localPosition.z);
    37.             SnapToConstraints();
    38.         }
    39.     }
    40.  
    41.     public void OnPointerDown(PointerEventData eventData)
    42.     {
    43.         if (Input.GetMouseButtonDown(0))
    44.         {
    45.             startPosX = PosOffset().x;
    46.             startPosY = PosOffset().y;
    47.  
    48.             isHeld = true;
    49.             TimeLineManager.tlm.itemHeld = true;
    50.         }
    51.     }
    52.  
    53.     private Vector2 MouseToPixels()
    54.     {
    55.         Vector2 mousePos = Camera.main.ScreenToViewportPoint(Input.mousePosition);
    56.         mousePos.x -= .5f;
    57.         mousePos.x *= 1920f / (1 + (zoom.desiredScale.x - zoom.startSize.x));
    58.         mousePos.y -= .5f;
    59.         mousePos.y *= 1080f / (1 + (zoom.desiredScale.y - zoom.startSize.y));
    60.         return mousePos;
    61.     }
    62.  
    63.     private Vector2 PosOffset()
    64.     {
    65.         Vector2 myPos = new Vector2(transform.localPosition.x, transform.localPosition.y);
    66.         myPos.x -= MouseToPixels().x;
    67.         myPos.y -= MouseToPixels().y;
    68.         return myPos;
    69.     }
    70.  
    71.     private void SnapToConstraints()
    72.     {
    73.         RectTransform myRect = GetComponent<RectTransform>();
    74.         Vector2 anchorPos = myRect.anchoredPosition;
    75.         float myx = anchorPos.x;
    76.         float myy = anchorPos.y;
    77.         myx = Mathf.Clamp(myx, -850f + (myRect.sizeDelta.x / 2), 850f - (myRect.sizeDelta.x / 2));
    78.         myy = Mathf.Clamp(myy, -500f + (myRect.sizeDelta.y / 2), 500f - (myRect.sizeDelta.y / 2));
    79.         anchorPos.x = myx;
    80.         anchorPos.y = myy;
    81.         myRect.anchoredPosition = anchorPos;
    82.     }
    83. }
    I've found a lot of things run smoothly when using Screen space, localPosition or AnchorPosition. I'm really looking for any way to get the edges of the TimelineHolder and translate that into the grandchild object's position. Sorry if this is a lot of code. I'm new to posting so I'd rather be more thorough than leave more questions unanswered. I've never been great with Unity's 2D stuff, but this project has me excited because it keeps pushing me out of my comfort zone in new and interesting ways. :)

    P.S. - My canvas is set to "Screen Space - Camera", plane distance of 1. The scaler's Scale Mode is set to "Scale with Screen Size" and match is 0.5. I've seen in other posts that that is sometimes important so I thought I'd provide that as well.