Skip to content

Instantly share code, notes, and snippets.

@Rovsau
Created April 16, 2023 19:45
Show Gist options
  • Save Rovsau/8aded2f81933528b6b0a223c83f06584 to your computer and use it in GitHub Desktop.
Save Rovsau/8aded2f81933528b6b0a223c83f06584 to your computer and use it in GitHub Desktop.
One-script-solution Radar for Rigidbody and 3D Colliders, with LayerMask

The Collider Radar detects all 3D Colliders included in the LayerMask.

  • Script is ideally placed on a child object of the player.
  • The player is required to have a Rigidbody for the physics events OnTriggerEnter/Exit.
  • A Radar UI object, background and icon prefab must be set up for this to work. (not provided)

This example does not include pooling, and simply Creates/Destroys icons as colliders enter and exit the trigger.

using System.Collections.Generic;
using UnityEngine;

namespace Rovsau.Unity.ColliderRadar
{
    [RequireComponent(typeof(SphereCollider))]
    public class ColliderRadar : MonoBehaviour
    {
        // ToDo:  Better to use one dictionary.
        private Dictionary<int, Transform> _trackedObjects = new Dictionary<int, Transform>();
        private Dictionary<int, RectTransform> _radarIcons = new Dictionary<int, RectTransform>();

        [Header("Prefab")]
        public GameObject _radarTargetIcon;

        [Header("Radar Object")]
        public RectTransform _radarIconContainer;

        [Header("Config")]
        [SerializeField] private LayerMask _radarLayers;
        [SerializeField] private float _range = 20f;

        [Tooltip("The additional range from which the icon will be visibly lingering at the outermost border.")]
        [SerializeField] private float _rangeFalloff = 10f;
        [SerializeField] private bool _applyRotation = true;

        [SerializeField] private Transform _player;

        private float _radarSize;
        private float _radarScale;

        private void Awake()
        {
            _player = transform;
            _radarSize = _radarIconContainer.rect.width * 0.5f;
            _radarScale = _radarSize / _range;

            GetComponent<SphereCollider>().radius = _range + _rangeFalloff;
        }

        private void FixedUpdate()
        {
            foreach (var (key, icon) in _radarIcons)
            {
                icon.anchoredPosition = GetIconLocation(_trackedObjects[key]);
            }
        }

        private void OnTriggerEnter(Collider other)
        {
            if (HasLayerInMask(other.gameObject.layer))
            {
                // Track object.
                int instanceID = other.transform.GetInstanceID();
                if (_trackedObjects.TryAdd(instanceID, other.transform))
                {
                    // Create radar icon.
                    RectTransform icon = Instantiate(_radarTargetIcon, _radarIconContainer).GetComponent<RectTransform>();
                    icon.localPosition = GetIconLocation(other.transform);
                    icon.name = other.name;

                    _radarIcons.Add(instanceID, icon);
                }
            }
        }

        private void OnTriggerExit(Collider other)
        {
            int instanceID = other.transform.GetInstanceID();
            if (_radarIcons.TryGetValue(instanceID, out RectTransform icon))
            {
                // Remove tracking.
                _trackedObjects.Remove(instanceID);
                _radarIcons.Remove(instanceID);
                Destroy(icon.gameObject);
            }
        }

        public bool HasLayerInMask(int layerIndex)
        {
            return (_radarLayers & (1 << layerIndex)) != 0;
        }

        private Vector2 GetIconLocation(Transform target)
        {
            // Get vector distance on a 2D plane (XZ). 
            Vector3 distance = target.position - _player.position;
            Vector2 location = new Vector2(distance.x, distance.z) * _radarScale;
            if (_applyRotation)
            {
                // Project player position on to an XZ plane. 
                Vector3 playerForwardDirectionXZ = Vector3.ProjectOnPlane(_player.forward, Vector3.up);
                Quaternion rotation = Quaternion.LookRotation(playerForwardDirectionXZ);

                // Flip Y axis. 
                rotation.eulerAngles = new Vector3(rotation.eulerAngles.x, -rotation.eulerAngles.y, rotation.eulerAngles.z);

                // Rotate in 3D space. 
                Vector3 rotatedIconLocation = rotation * new Vector3(location.x, 0, location.y);

                // Convert from 3D to 2D (XZ)
                location = new Vector2(rotatedIconLocation.x, rotatedIconLocation.z);
            }

            // Keep within max radius. 
            location = Vector2.ClampMagnitude(location, _radarSize);
            return location;
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment