Search code examples
c#unity-game-engineunity-ui

Draw bounding rectangle (screen space) around a game object with a renderer (world space) (Unity)


I have a game object that lives in world space in my scene. I would like to get the coordinates of the corners of the bounding rectangle for this game object's renderer in screen space because I have UI elements that want to be positioned around this box.

Context: I'm making a tutorial and I am using panels to darken everything except for a game object that will be left un-darkened. I can do this easily with buttons that already live in screen space and have rect transforms on them, but I can't figure out how to do this around a game object in world space. We are using a camera with orthographic projection and are using Unity version 2019.2.17f1.

Here's what I've tried:

public void FocusOnRenderer(Renderer renderer) {

        // left, top, right, and bottom are Panels whose pivots are set as follows:
        // top: (1, 0)
        // right: (0, 0)
        // bottom: (0, 1)
        // left: (1, 1)
        // so when their positions are set to be the corners of the target bounding box, they will fit together nicely.

        left.gameObject.SetActive(true);
        top.gameObject.SetActive(true);
        right.gameObject.SetActive(true);
        bottom.gameObject.SetActive(true);

        Vector3 center = HandleUtility.WorldToGUIPoint(renderer.bounds.center); // center of bounding box
        Vector3 halfSize = HandleUtility.WorldToGUIPoint(renderer.bounds.extents)); // half size of bounding box

        Vector3 topRight = center + halfSize;
        Vector3 topLeft = center  + new Vector3(-halfSize.x, halfSize.y, halfSize.z);
        Vector3 bottomRight = center  + new Vector3(halfSize.x, -halfSize.y, halfSize.z);
        Vector3 bottomLeft = center  + new Vector3(-halfSize.x, -halfSize.y, halfSize.z);

        left.position = topLeft;
        top.position = topRight;
        right.position = bottomRight;
        bottom.position = bottomLeft;
    }

I think this is wrong because what I'm doing with the renderer's bounds to compute halfSize and center aren't giving me a bounding rectangle. I was hoping there would be an easy built in way to do this but I haven't been able to find anything so far.

Thank you for your help!


Solution

  • I found an answer (with video suggestion from @SparrowsNest on the Unity Forum)! Here's the video starting at the relevant timestamp: https://youtu.be/2Tgqr1_ajqE?t=1061

    Steps:

    1. get corners of bounding box from the renderer's bounds
    2. convert those corners into screen space
    3. get the min and max x and y values
    4. set my panels' positions using those min and max x and y values

    Here's my code:

        public void FocusOnBounds(Bounds bounds) {
            
            // left, top, right, and bottom are Panels whose pivots are set as follows:
            // top: (1, 0)
            // right: (0, 0)
            // bottom: (0, 1)
            // left: (1, 1)
            // so when their positions are set to be the corners of the target bounding box, they will fit together nicely.
    
            left.gameObject.SetActive(true);
            top.gameObject.SetActive(true);
            right.gameObject.SetActive(true);
            bottom.gameObject.SetActive(true);
    
            Vector3 c = bounds.center;
            Vector3 e = bounds.extents;
    
            Vector3[] worldCorners = new [] {
                new Vector3( c.x + e.x, c.y + e.y, c.z + e.z ),
                new Vector3( c.x + e.x, c.y + e.y, c.z - e.z ),
                new Vector3( c.x + e.x, c.y - e.y, c.z + e.z ),
                new Vector3( c.x + e.x, c.y - e.y, c.z - e.z ),
                new Vector3( c.x - e.x, c.y + e.y, c.z + e.z ),
                new Vector3( c.x - e.x, c.y + e.y, c.z - e.z ),
                new Vector3( c.x - e.x, c.y - e.y, c.z + e.z ),
                new Vector3( c.x - e.x, c.y - e.y, c.z - e.z ),
            };
    
            IEnumerable<Vector3> screenCorners = worldCorners.Select(corner => Camera.main.WorldToScreenPoint(corner));
            float maxX = screenCorners.Max(corner => corner.x);
            float minX = screenCorners.Min(corner => corner.x);
            float maxY = screenCorners.Max(corner => corner.y);
            float minY = screenCorners.Min(corner => corner.y);
    
            Vector3 topRight = new Vector3(maxX, maxY, 0);
            Vector3 topLeft = new Vector3(minX, maxY, 0);
            Vector3 bottomRight = new Vector3(maxX, minY, 0);
            Vector3 bottomLeft = new Vector3(minX, minY, 0);
    
            left.position = topLeft;
            top.position = topRight;
            right.position = bottomRight;
            bottom.position = bottomLeft;
        }