From f5de233e0c05d87f84f8dd5cd313b9ca74b0b6df Mon Sep 17 00:00:00 2001 From: MelekTaus Moribundus Date: Sun, 5 Jul 2026 20:46:58 +0200 Subject: [PATCH] Add radar blip system: Radar spawner + directional RadarTarget Radar.Spawn(transform, lifetime) clones the "Target Template", names it "Target" and attaches a RadarTarget that self-destructs after lifetime. RadarTarget aims each frame toward camera->target (full 3D LookRotation), drives the point renderer's _BaseColor from a forward-bearing gradient (ahead/side/behind depth cue lost in the flat disc projection) via a MaterialPropertyBlock, and scales the point renderer transform by camera->target distance. Co-Authored-By: Claude Opus 4.8 --- Assets/Scripts/Components/Radar.cs | 56 ++++++++++ Assets/Scripts/Components/Radar.cs.meta | 2 + Assets/Scripts/Components/RadarTarget.cs | 100 ++++++++++++++++++ Assets/Scripts/Components/RadarTarget.cs.meta | 2 + 4 files changed, 160 insertions(+) create mode 100644 Assets/Scripts/Components/Radar.cs create mode 100644 Assets/Scripts/Components/Radar.cs.meta create mode 100644 Assets/Scripts/Components/RadarTarget.cs create mode 100644 Assets/Scripts/Components/RadarTarget.cs.meta diff --git a/Assets/Scripts/Components/Radar.cs b/Assets/Scripts/Components/Radar.cs new file mode 100644 index 0000000..aae5037 --- /dev/null +++ b/Assets/Scripts/Components/Radar.cs @@ -0,0 +1,56 @@ +using UnityEngine; + +/// +/// Sits on the "Radar" overlay object (a disc rendered by the Radar Camera in front of the main camera). +/// Spawns "Target" blips - clones of the child "Target Template" - each of which continuously points from +/// the reference camera toward a world-space transform (visualising the bearing camera -> target) and then +/// destroys itself after a given lifetime. See for the per-blip behaviour. +/// +[DisallowMultipleComponent] +public sealed class Radar : MonoBehaviour +{ + [Tooltip("Template each spawned Target is cloned from. Auto-resolves to the child \"Target Template\".")] + [SerializeField] private Transform targetTemplate; + + [Tooltip("Camera the direction is measured FROM (camera -> target). Auto-resolves to the Camera this radar sits under.")] + [SerializeField] private Camera referenceCamera; + + private void Awake() + { + if (!targetTemplate) + { + var t = transform.Find("Target Template"); + if (t) targetTemplate = t; + } + if (!referenceCamera) referenceCamera = GetComponentInParent(); + } + + /// + /// Spawns a Target blip that each frame orients toward (from the reference + /// camera) and destroys itself after seconds. Returns the spawned blip, or + /// null if it could not be created. + /// + public RadarTarget Spawn(Transform target, float lifetime) + { + if (!target) + { + Debug.LogWarning("[Radar] Spawn called without a target transform.", this); + return null; + } + if (!targetTemplate) + { + Debug.LogWarning("[Radar] No Target Template assigned or found.", this); + return null; + } + + Transform clone = Instantiate(targetTemplate, targetTemplate.parent); + clone.name = "Target"; + clone.localPosition = targetTemplate.localPosition; + clone.localScale = targetTemplate.localScale; + clone.gameObject.SetActive(true); + + var blip = clone.GetComponent(); + blip.Init(target, referenceCamera, lifetime); + return blip; + } +} diff --git a/Assets/Scripts/Components/Radar.cs.meta b/Assets/Scripts/Components/Radar.cs.meta new file mode 100644 index 0000000..83ec13a --- /dev/null +++ b/Assets/Scripts/Components/Radar.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b8be89860f43b3240bb09cd9931ceaf2 \ No newline at end of file diff --git a/Assets/Scripts/Components/RadarTarget.cs b/Assets/Scripts/Components/RadarTarget.cs new file mode 100644 index 0000000..f519796 --- /dev/null +++ b/Assets/Scripts/Components/RadarTarget.cs @@ -0,0 +1,100 @@ +using UnityEngine; + +/// +/// A single radar blip spawned by . Each frame it orients itself so its forward (+Z, where +/// the "Point" marker sits) faces the direction from the reference camera to a world-space target, visualising +/// the bearing camera -> target. Tracks continuously (the camera turns / the target moves) and self-destructs +/// once its lifetime elapses - or earlier if its target is destroyed. The point's base colour and size encode +/// the two things the flat radar disc can't show via position alone: tints it by +/// how far the target sits ahead of / behind the camera, and its scale shrinks with camera -> target distance. +/// +[DisallowMultipleComponent] +public sealed class RadarTarget : MonoBehaviour +{ + public Renderer pointRenderer; + + [Tooltip("Point tint by bearing: sampled at 0 when the target is dead ahead of the camera, at 1 when directly behind. Flip the keys for the reverse.")] + [SerializeField] Gradient forwardGradient; + [Tooltip("Point transform scale at the near (x) and far (y) end of pointDistanceRange.")] + [SerializeField] Vector2 pointDistanceScale; + [Tooltip("camera -> target distance mapped onto pointDistanceScale: x = near distance, y = far distance.")] + [SerializeField] Vector2 pointDistanceRange; + + private Transform _target; + private Camera _camera; + private float _lifetime; + private float _age; + private bool _initialised; + + private MaterialPropertyBlock _mpb; + private static readonly int BaseColorId = Shader.PropertyToID("_BaseColor"); + + public Transform Target => _target; + public float RemainingLifetime => Mathf.Max(0f, _lifetime - _age); + + public void Init(Transform target, Camera referenceCamera, float lifetime) + { + _target = target; + _camera = referenceCamera; + _lifetime = lifetime; + _age = 0f; + _initialised = true; + Aim(); + } + + // LateUpdate so the aim uses the camera's final position/rotation for this frame. + private void LateUpdate() + { + if (!_initialised) return; + + _age += Time.deltaTime; + if (_age >= _lifetime) + { + Destroy(gameObject); + return; + } + + // The world target may despawn (e.g. a caught hider) before this blip expires - drop it, don't dangle. + if (!_target) + { + Destroy(gameObject); + return; + } + + Aim(); + } + + private void Aim() + { + if (!_target || !_camera) return; + Vector3 dir = _target.position - _camera.transform.position; + float sqr = dir.sqrMagnitude; + if (sqr < 1e-8f) return; // camera sitting on the target -> keep the last orientation/colour/scale + + transform.rotation = Quaternion.LookRotation(dir); + + float distance = Mathf.Sqrt(sqr); + ApplyDistanceScale(distance); + ApplyForwardColor(dir / distance); + } + + // Shrinks the blip with distance: pointDistanceRange (near, far) remapped onto pointDistanceScale (near, far), clamped. + private void ApplyDistanceScale(float distance) + { + if (!pointRenderer) return; + float t = Mathf.InverseLerp(pointDistanceRange.x, pointDistanceRange.y, distance); + float s = Mathf.Lerp(pointDistanceScale.x, pointDistanceScale.y, t); + pointRenderer.transform.localScale = new Vector3(s, s, s); + } + + // Tints the point's base colour by how far the target sits ahead of (0) vs directly behind (1) the camera. + private void ApplyForwardColor(Vector3 dirNormalized) + { + if (!pointRenderer || forwardGradient == null) return; + float t = Vector3.Angle(_camera.transform.forward, dirNormalized) / 180f; + _mpb ??= new MaterialPropertyBlock(); + pointRenderer.GetPropertyBlock(_mpb); + _mpb.SetColor(BaseColorId, forwardGradient.Evaluate(t)); + pointRenderer.SetPropertyBlock(_mpb); + } +} diff --git a/Assets/Scripts/Components/RadarTarget.cs.meta b/Assets/Scripts/Components/RadarTarget.cs.meta new file mode 100644 index 0000000..bfdea7b --- /dev/null +++ b/Assets/Scripts/Components/RadarTarget.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b4a573fc0fda31d42aec388198183b81 \ No newline at end of file