Drawing a rectangle around an object on screen

Hi all,

I am trying to achieve an effect where a window will pop up on screen next to an object with it’s information when the mouse is hovering over a gameobject.

The gameobject has a collider with IsTrigger = true. So onMouseXXX works.

It is easy to get the position of the object on screen but I am struggling to calculate the size of the object in screen co-ordinates and especially when the camera moves.

My code so far is:

using UnityEngine;


public class SelectionScript : MonoBehaviour
{
    private bool IsMouseOver;
    private Rect WindowRect = new(0, 0, 100, 100);
    private int WindowID;
    private string WindowName;
    Renderer _renderer;

    private void Start()
    {
        WindowID = GetHashCode();
        WindowName = gameObject.name;
        _renderer = GetComponent<Renderer>();
    }

    private void OnGUI()
    {
        if (!IsMouseOver) return;

        Rect rect = new Rect();
        rect.position = WorldToGuiPoint(gameObject.transform.position);
        // Ok, fine so far but how do I find the size o fthe object in screen space co-ordinates?
        // I know I can use renderer.bounds. min and max but this doesn't seem to take perspective into consideration..
        rect.width = 100;
        rect.height = 100;
        Utils.DrawScreenRectBorder(rect, 2, new Color(0.0f, 0.0f, 0.0f)); // This is a seperate script but works.
        // I would like the onGUI window to pop up on the top Right of the object and not obscure it (if it does
        // this can lead to a flickering effect if the cursor is over the object but also in the window as the
        // onMouseOver toggles on/off eavery other frame)
        WindowRect = GUILayout.Window(WindowID, WindowRect, WindowFunc, WindowName);
    }

    void WindowFunc(int id)
    {
        GUILayout.Label("Hello World");
    }

    private void OnMouseOver()
    {
        IsMouseOver = true;
    }

    private void OnMouseExit()
    {
        IsMouseOver = false;
    }

    public Vector2 WorldToGuiPoint(Vector3 GOposition)
    {
        Vector2 guiPosition = Camera.main.WorldToScreenPoint(GOposition);
        guiPosition.y = Screen.height - guiPosition.y;
        return guiPosition;
    }

    // Not used but for investigation purposes!
    public void OnDrawGizmos()
    {
        Renderer r = GetComponent<Renderer>();
        if (r == null)
            return;
        Gizmos.matrix = Matrix4x4.identity;
        Gizmos.color = Color.blue;
        Gizmos.DrawWireCube(r.bounds.center, r.bounds.extents * 2);
    }

}

Has anyone solved this problem or have any good ideas? Thank you in advance.

Michael

[Solved] (In a way I like anyway…!) Others may find it useful. :slight_smile:

using UnityEngine;

public class SelectionScript : MonoBehaviour
{

    public bool IsSelected;
    private bool IsMouseOver;

    private Rect WindowRect;
    private int WindowID;
    private string WindowName;
    Renderer _renderer;
    Color rectColour = Color.green;
    Vector2[] points;

    private void Start()
    {
        WindowID = GetHashCode();
        WindowName = gameObject.name;
        _renderer = GetComponent<Renderer>();
        points = new Vector2[8];
    }

    private void OnGUI()
    {
        if (!IsSelected) if (!IsMouseOver) return;

        Rect rect = Rect.zero;
        rect.position = WorldToGuiPoint(_renderer.bounds.center);

        float x = _renderer.bounds.extents.x;
        float y = _renderer.bounds.extents.y;
        float z = _renderer.bounds.extents.z;

        points[0] = WorldToGuiPoint(_renderer.bounds.center + new Vector3(x, y, z));
        points[1] = WorldToGuiPoint(_renderer.bounds.center + new Vector3(x, -y, z));
        points[2] = WorldToGuiPoint(_renderer.bounds.center + new Vector3(x, y, -z));
        points[3] = WorldToGuiPoint(_renderer.bounds.center + new Vector3(x, -y, -z));
        points[4] = WorldToGuiPoint(_renderer.bounds.center + new Vector3(-x, y, z));
        points[5] = WorldToGuiPoint(_renderer.bounds.center + new Vector3(-x, -y, z));
        points[6] = WorldToGuiPoint(_renderer.bounds.center + new Vector3(-x, y, -z));
        points[7] = WorldToGuiPoint(_renderer.bounds.center + new Vector3(-x, -y, -z));

        foreach (Vector2 point in points)
        {
            rect = GrowRectangleToInlcudePoint(rect, point);
        }

        Utils.DrawScreenRectBorder(rect, 2, rectColour);

        WindowRect.position = new Vector2(rect.xMax, rect.yMin - WindowRect.height);
        WindowRect = GUILayout.Window(WindowID, WindowRect, WindowFunc, WindowName, GUILayout.MinWidth(80));
    }

    void WindowFunc(int id)
    {
        GUILayout.Label("Hello World", GUILayout.MinWidth(100));
    }

    private void OnMouseEnter()
    {
        IsMouseOver = true;
    }

    private void OnMouseExit()
    {
        IsMouseOver = false;
    }

    private void OnMouseDown()
    {
        IsSelected = !IsSelected;
        if (IsSelected)
        {
            rectColour = Color.red;
        }
        else
        {
            rectColour = Color.green;
        }
    }

    public Vector2 WorldToGuiPoint(Vector3 pos)
    {
        Vector2 guiPosition = Camera.main.WorldToScreenPoint(pos);
        guiPosition.y = Screen.height - guiPosition.y;
        return guiPosition;
    }


    Rect GrowRectangleToInlcudePoint(Rect rect, Vector2 p)
    {
        if (!rect.Contains(p))
        {
            if (p.x < rect.xMin)
                rect.xMin = p.x;
            else if (p.x > rect.xMax)
                rect.xMax = p.x;

            if (p.y < rect.yMin)
                rect.yMin = p.y;
            else if (p.y > rect.yMax)
                rect.yMax = p.y;
        }
        return rect;
    }

    // Not used but for investigation purposes!
    //public void OnDrawGizmos()
    //{
    //    Renderer r = GetComponent<Renderer>();
    //    if (r == null)
    //        return;
    //    Gizmos.matrix = Matrix4x4.identity;
    //    Gizmos.color = Color.blue;
    //    Gizmos.DrawWireCube(r.bounds.center, r.bounds.extents * 2);
    //    Gizmos.DrawSphere(r.bounds.center, 0.1f);
    //    float x = r.bounds.extents.x;
    //    float y = r.bounds.extents.y;
    //    float z = r.bounds.extents.z;
    //    Gizmos.color = Color.yellow;
    //    Gizmos.DrawSphere(r.bounds.center + new Vector3(x, y, z), 0.1f);
    //    Gizmos.color = Color.blue;
    //    Gizmos.DrawSphere(r.bounds.center + new Vector3(x, -y, z), 0.1f);
    //    Gizmos.DrawSphere(r.bounds.center + new Vector3(x, y, -z), 0.1f);
    //    Gizmos.DrawSphere(r.bounds.center + new Vector3(x, -y, -z), 0.1f);
    //    Gizmos.DrawSphere(r.bounds.center + new Vector3(-x, y, z), 0.1f);
    //    Gizmos.DrawSphere(r.bounds.center + new Vector3(-x, -y, z), 0.1f);
    //    Gizmos.DrawSphere(r.bounds.center + new Vector3(-x, y, -z), 0.1f);
    //    Gizmos.DrawSphere(r.bounds.center + new Vector3(-x, -y, -z), 0.1f);
    //}

}

Oh, and Utils is…(which I’ve lifted form somewhere but I can’t remember who coded it originally - sorry)

using UnityEngine;

public static class Utils
{
    public static Rect GetScreenRect(Vector3 screenPosition1, Vector3 screenPosition2)
    {
        // Move origin from bottom left to top left
        screenPosition1.y = Screen.height - screenPosition1.y;
        screenPosition2.y = Screen.height - screenPosition2.y;
        // Calculate corners
        var topLeft = Vector3.Min(screenPosition1, screenPosition2);
        var bottomRight = Vector3.Max(screenPosition1, screenPosition2);
        // Create Rect
        return Rect.MinMaxRect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y);
    }

    public static Bounds GetViewportBounds(Camera camera, Vector3 screenPosition1, Vector3 screenPosition2)
    {
        var v1 = Camera.main.ScreenToViewportPoint(screenPosition1);
        var v2 = Camera.main.ScreenToViewportPoint(screenPosition2);
        var min = Vector3.Min(v1, v2);
        var max = Vector3.Max(v1, v2);

        min.z = camera.nearClipPlane;
        max.z = camera.farClipPlane;

        var bounds = new Bounds();

        bounds.SetMinMax(min, max);

        return bounds;
    }

    public static void DrawScreenRect(Rect rect, Color color)
    {
        GUI.color = color;
        GUI.DrawTexture(rect, Texture2D.whiteTexture);
        GUI.color = Color.white;
    }

    public static void DrawScreenRectBorder(Rect rect, int thickness, Color color)
    {
        Utils.DrawScreenRect(new Rect(rect.xMin, rect.yMin, rect.width, thickness), color);
        Utils.DrawScreenRect(new Rect(rect.xMin, rect.yMin, thickness, rect.height), color);
        Utils.DrawScreenRect(new Rect(rect.xMax - thickness, rect.yMin, thickness, rect.height), color);
        Utils.DrawScreenRect(new Rect(rect.xMin, rect.yMax - thickness, rect.width, thickness), color);
    }
}