Implementing a Selection Visual Indicator in Unity

Published

Working on my ARPG, I wanted to create an indicator to show that an object was being focused, much like what’s done in Wind Waker, and you can see the result below.

Visual Selection Indicator
The End Result

For the shader, I created a new shadergraph, in which I implemented billboarding: In this previous article, I talked about creating a billboard in shadergraph.. Then for the texture I used two rotated squares to create a chevron and offset the UV position using a sine function.

Then I created a controller to set the visual element itself. It needed to be set above the target object, so it was a good idea to the renderer object of the target and use its bounds to get the target position. For this, we get the bounds center of the object (vector 3), then we replace the y element (the height) of this vector3 to be equals to the max Y value of the bounds of the object. Then, since we are rendering on a quad, we add half the local scale of the quad to set the quad as floating above the object.

Now you’ll only need to add some logic in your controller to enable the VisualSelectionController and call SetToTarget.

Quick note: we are using GetComponentInChildren here since objects don’t necessarily have their renderer on the top level of their hierarchy.

using UnityEngine;

public class VisualSelectionController : MonoBehaviour
{
    [SerializeField]
    private Renderer _mRenderer = null;

    private void Awake()
    {
        if (_mRenderer == null)
        {
            _mRenderer = GetComponent<Renderer>();
            
        }

    }

    private void OnEnable()
    {
        _mRenderer.enabled = true;

    }

    private void OnDisable()
    {
        _mRenderer.enabled = false;

    }

    public void SetToTarget(Transform targetObject)
    {
        transform.parent = targetObject;

        // try to get renderer and set position to top of bounds
        Renderer targetRenderer = null;

        targetRenderer = targetObject.GetComponentInChildren<Renderer>(false);
        //mTargetObject.TryGetComponent<Renderer>(out targetRenderer);

        if (targetRenderer == null)
        {
            Debug.Log("No renderer found");

        } else
        {
            Vector3 tempPos = targetRenderer.bounds.center;
            tempPos.y = targetRenderer.bounds.max.y + transform.localScale.y * 0.5f;

            transform.position = tempPos;

        }
    }
}