Colocar un elemento de la interfaz sobre un objeto del juego

En este tutorial vamos a ver como hacer que un elemento de la interfaz se posicione fijado sobre un objeto del juego.
Se trata de un efecto muy habitual en videojuegos y que se suele usar para mostrar la vida o cualquier otra propiedad de un personaje, cuadros de diálogo, etc.

Empezaremos con una escena muy sencilla, simplemente un personaje (en este caso una cápsula) que se puede mover por la escena y panel con un texto en un Canvas.

Panel con la jerarquía de la escena
Vista de la escena

Lo que pretendemos es el panel se coloque sobre el personaje y que al moverse este, le acompañe como si estuviera pegado a él.

Creamos un script PinUIElement.cs

using UnityEngine;

public class PinUIElement : MonoBehaviour
{
    public Transform worldPosition;

    new public Camera camera;

    RectTransform rect;

    private void Start()
    {
        rect = GetComponent<RectTransform>();
        if(camera == null)
        {
            camera = Camera.main;
        }
    }

    private void LateUpdate()
    {
        if(worldPosition)
        {
            Vector2 pos = camera.WorldToViewportPoint(worldPosition.position);
            rect.anchorMax = pos;
            rect.anchorMin = pos;
        }
    }
}

Veamos el código paso a paso:

Definimos un campo público para asignar un Transform que nos servirá de referencia para posicionar el elemento.

public Transform worldPosition;

Otro campo, también público para asignar la cámara que se usará para calcular la posición del elemento. La palabra clave new es necesaria ya que camera es una propiedad obsoleta de la clase MonoBehaviour

new public Camera camera;

Por último, un campo privado para la referencia al componente RectTransform del elemento de la UI que queremos controlar

RectTransform rect;

En Start asignamos a rect el componente correspondiente y, si no se ha asignado en el inspector ninguna cámara, le asignamos a camera la Main Camera que haya en la escena.

private void Start()
{
    rect = GetComponent<RectTransform>();
    if(camera == null)
    {
        camera = Camera.main;
    }
}

Por último, para evitar efectos indeseados en caso de que se mueva la cámara, en el LateUpdate con WorldToViewportPoint obtenemos la posición en el viewport de la cámara del Transform, y aplicamos este valor a los anchors del elemento de la UI.

private void LateUpdate()
    {
        if(worldPosition)
        {
            Vector2 pos = camera.WorldToViewportPoint(worldPosition.position);
            rect.anchorMax = pos;
            rect.anchorMin = pos;
        }
    }

El motivo de hacerlo así, controlando el viewport y los anchors, es que así el script funcionará correctamente tanto si el Canvas en el que está el elemento se renderiza como Screen Space – Overlay o como Screen Space – Camera.

En Unity creamos un GameObject vacío como hijo del personaje y colocamos un poco por encima de su cabeza.

Después, al Panel le añadimos el script PinUIElement y en su campo World Position colocamos el GameObject que acabamos de crear

Si probamos la escena, veremos como el Panel permanece fijado al personaje al moverse este.