Drawing a bounding box around UI Elements in Unity

122 Views Asked by At

I'm attempting to get the pixel-perfect bounding box for multiple UI elements in my scene, ensuring there are no empty spaces around the sprites. Is it possible to achieve this without checking for each individual pixel?

private void OnDrawGizmos()
{
    var min = Vector3.positiveInfinity;
    var max = Vector3.negativeInfinity;

    foreach (var image in imageList)
    {
        if(!image) continue;

        // Get the 4 corners in world coordinates
        var v = new Vector3[4];
        image.rectTransform.GetWorldCorners(v);

        // update min and max
        foreach (var vector3 in v)
        {
            min = Vector3.Min(min, vector3);
            max = Vector3.Max(max, vector3);
        }
        
    }
    // create the bounds
    var bounds = new Bounds();
    bounds.SetMinMax(min, max);
    Gizmos.color = Color.red;
    Gizmos.DrawWireCube(bounds.center, bounds.extents*2);
}

This code produces the red box in this image below. But, what I'm after is the yellow box.

enter image description here enter image description here

(Sprites are generated by user)

2

There are 2 best solutions below

1
Artak On

Maybe you can add PolygonCollider2D on them, and configure editor time. Then at the runtime, you can create new Bounds, and get bounds of all colliders Collider2D.Bounds, and use Bounds.Encapsulate.

P.S. I am not sure if will it work for a UI Element...

0
hijinxbassist On

You can use the images RectTransform to get the position and sizeDeltas. Then use those to construct the bounds.

public class UIBounds : MonoBehaviour
{
    public Image[] images;

    private void Start()
    {
        var parent = images[0].transform.parent;
        var bounds = new Bounds(
            images[0].rectTransform.position, 
            images[0].rectTransform.sizeDelta
        );

        for (int i = 1; i < images.Length; i++)
        {
            bounds.Encapsulate(
                new Bounds(
                    images[i].rectTransform.position, 
                    images[i].rectTransform.sizeDelta
                )
            );
        }

        var newImage = new GameObject("Bounds Image").AddComponent<Image>();

        newImage.rectTransform.SetParent(parent);
        newImage.rectTransform.SetAsFirstSibling();
        newImage.rectTransform.position = bounds.center;
        newImage.rectTransform.sizeDelta = bounds.size;
    }
}

The bounds are constructed using the first image of the array, then encapsulating all other images bounds. (this prevents the origin point from being included in the bounds creation.

I added a new image to visualize the bounds created by the above code:

enter image description here

The resulting bounds is quite rudimentary, and will not work in all cases. The above was tested with a square image+sprite. Long rectangles or sprites that extend to all edges of the image will likely be cut off.

To get perfect bounds, you would need to sample the pixels of the source and handle the rotations.