ComponentRadar tracks all objects with a RadarComponent.
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Rovsau.Unity.ComponentRadar
{
// ComponentRadar tracks all objects with a RadarComponent.
// Execution order: Must run after all units have moved.
// Execution order: Must run before RadarComponent.
// In its current form, there is no way to disable components that are far out of range.
// It should probably be adapted when in a proper gameplay context.
// Radar Size is currently determined by the RectTransform size, not the Image size itself.
public class ComponentRadar : MonoBehaviour
{
public static GameObject __RadarTargetIcon;
public static RectTransform __RadarIconContainer;
private static Dictionary<int, RadarComponent> __trackers = new Dictionary<int, RadarComponent>();
[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, Range(0f, 2f)] private float _updateFrequency = 0f;
[Header("Radar Icon Parent Container")]
[SerializeField] private RectTransform _radarIconContainer;
[Header("Prefab")]
[SerializeField] private GameObject _radarTargetIconPrefab;
private Transform _player;
private float _radarSize;
private float _radarScale;
private float _timer;
private float _rangeSquared;
private void Awake()
{
_player = transform;
_radarSize = _radarIconContainer.rect.width * 0.5f;
_radarScale = _radarSize / _range;
float rangeWithFalloff = _range + _rangeFalloff;
_rangeSquared = rangeWithFalloff * rangeWithFalloff;
__RadarTargetIcon = _radarTargetIconPrefab;
__RadarIconContainer = _radarIconContainer;
}
// Should be FixedUpdate in a Multiplayer setting.
private void LateUpdate()
{
_timer += Time.deltaTime;
if (_timer >= _updateFrequency)
{
_timer -= _updateFrequency;
UpdateRadar();
}
}
private void UpdateRadar()
{
foreach (RadarComponent tracker in __trackers.Values)
{
Vector3 p = _player.position;
Vector3 t = tracker.Target.position;
float x = p.x - t.x;
float y = p.y - t.y;
float z = p.z - t.z;
// Compare Euclidean distance.
// Same relative ordering as actual distance.
if (x * x + y * y + z * z > _rangeSquared)
{
// Out of range.
tracker.RadarIcon.gameObject.SetActive(false);
}
else
{
// If icon was disabled, and component is active, set icon active.
if (!tracker.RadarIcon.gameObject.activeInHierarchy && tracker.enabled) tracker.RadarIcon.gameObject.SetActive(true);
tracker.RadarIcon.localPosition = GetIconLocation(tracker.Target);
}
}
}
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.
// If falloff is zero, this line can be commented/disabled.
location = Vector2.ClampMagnitude(location, _radarSize);
return location;
}
public static void Add(int instanceID, RadarComponent radarComponent)
{
__trackers.Add(instanceID, radarComponent);
}
public static void Remove(int instanceID)
{
__trackers.Remove(instanceID);
}
}
}
using UnityEngine;
namespace Rovsau.Unity.ComponentRadar
{
// Execution order: Must run after ComponentRadar.
[DefaultExecutionOrder(1)]
[DisallowMultipleComponent]
public class RadarComponent : MonoBehaviour
{
public int InstanceID { get; private set; }
public Transform Target { get; private set; }
public RectTransform RadarIcon { get; private set; }
private void Awake()
{
InstanceID = GetInstanceID();
Target = transform;
RadarIcon = Instantiate(ComponentRadar.__RadarTargetIcon, ComponentRadar.__RadarIconContainer).GetComponent<RectTransform>();
}
private void OnEnable()
{
ComponentRadar.Add(InstanceID, this);
}
private void OnDisable()
{
ComponentRadar.Remove(InstanceID);
}
private void OnDestroy()
{
if (RadarIcon) Destroy(RadarIcon.gameObject);
}
}
}