Files
adminandClaude Fable 5 6c03e5d3f6 Refactor Assets/Scripts: trim restating comments, minor structure cleanups
Sweep over 48 files (Editor/ excluded): remove comments that restate the
code, condense class summaries, fix three stale doc statements
(HsvColorPicker alpha bar, PaintHud layoutPrefab, MasterVolume
FallbackDefault). Inline two single-use wrappers (IsPointerOverUi,
BuildModes), drop an unused using, replace a magic literal with the
existing ParallaxBase constant, and convert ~10 UnityEngine.Object null
checks to the implicit-bool convention. No serialized fields, signatures,
RPC/NetworkVariable names, defaults, or #if blocks touched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 08:29:18 +02:00

196 lines
8.9 KiB
C#

using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 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 pointing from the reference
/// camera toward a world-space transform until its lifetime elapses (see <see cref="RadarTarget"/>).
///
/// Shown only while useful: at least one live target, first-person active, AND the match in its Seek
/// phase. Instead of snapping on/off it fades: a shared 0..1 factor (<see cref="CurrentFade"/>) drives
/// the alpha of every radar renderer - the disc here, the blip points in RadarTarget - and scales the
/// disc from <see cref="hiddenScale"/> up to 1. The overlay camera is switched off only once fully faded
/// out, so hidden blips keep counting down their lifetime meanwhile.
/// </summary>
[DisallowMultipleComponent]
public sealed class Radar : MonoBehaviour
{
/// <summary>The scene's single radar overlay (lives in the persistent Main scene). Hunter clients spawn
/// hider blips onto it from MatchManager's radar-ping sweep.</summary>
public static Radar Instance { get; private set; }
[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;
[Tooltip("Overlay camera that renders the radar. Its rendering is toggled off unless the radar should show " +
"(at least one live target AND first-person active). Auto-resolves to the Camera this radar sits under.")]
[SerializeField] private Camera radarCamera;
[Tooltip("First-person rig. The radar is only shown while first-person is active. Empty = found automatically.")]
[SerializeField] private FirstPersonController firstPerson;
[Header("Fade")]
[Tooltip("Fade-in speed in alpha units per second (0 -> 1). e.g. 5 = ~0.2s to fully appear.")]
[SerializeField] private float fadeInSpeed = 5f;
[Tooltip("Fade-out speed in alpha units per second (1 -> 0). e.g. 3 = ~0.33s to fully vanish.")]
[SerializeField] private float fadeOutSpeed = 3f;
[Tooltip("Disc scale multiplier while fully hidden (fade 0); grows to 1 as it fades in, and back down on fade-out.")]
[SerializeField] private float hiddenScale = 0.8f;
// Live blips, pruned as they self-destruct; the radar shows only while at least one is alive.
private readonly List<RadarTarget> _blips = new List<RadarTarget>();
// Shared 0..1 visibility factor: drives disc/blip alpha and disc scale. Ramped in LateUpdate.
private float _fade;
/// <summary>Current 0..1 fade factor. Blips multiply their point alpha by this so they fade in step with the disc.</summary>
public float CurrentFade => _fade;
private Vector3 _baseScale; // authored disc scale, captured once; fade lerps hiddenScale..1 on top
private Renderer[] _staticRenderers; // radar's own renderers (the disc); the template subtree is excluded
private Color[] _staticBaseColors; // their full-opacity _BaseColor; alpha is scaled by _fade each frame
private MaterialPropertyBlock _mpb;
private static readonly int BaseColorId = Shader.PropertyToID("_BaseColor");
private void Awake()
{
Instance = this;
if (!targetTemplate)
{
var t = transform.Find("Target Template");
if (t) targetTemplate = t;
}
if (!referenceCamera) referenceCamera = GetComponentInParent<Camera>();
if (!radarCamera) radarCamera = GetComponentInParent<Camera>();
if (!firstPerson) firstPerson = FindFirstObjectByType<FirstPersonController>(FindObjectsInactive.Include);
_baseScale = transform.localScale;
CacheStaticRenderers();
_fade = 0f; // hidden until a target exists, first-person is live AND the match is seeking
ApplyFade();
}
// Gathers the radar's own renderers (the disc) once, with their full-opacity _BaseColor, so their alpha can be
// scaled by _fade each frame. The "Target Template" subtree is skipped: it is the blip prototype, and its clones
// fade themselves via RadarTarget (which owns their per-blip colour).
private void CacheStaticRenderers()
{
var all = GetComponentsInChildren<Renderer>(true);
var renderers = new List<Renderer>(all.Length);
var colors = new List<Color>(all.Length);
foreach (var r in all)
{
if (targetTemplate && r.transform.IsChildOf(targetTemplate)) continue;
renderers.Add(r);
var mat = r.sharedMaterial;
colors.Add(mat && mat.HasProperty(BaseColorId) ? mat.GetColor(BaseColorId) : Color.white);
}
_staticRenderers = renderers.ToArray();
_staticBaseColors = colors.ToArray();
}
private void OnDestroy()
{
if (Instance == this) Instance = null;
}
/// <summary>
/// Spawns a Target blip that each frame orients toward <paramref name="target"/> (from the reference
/// camera) and destroys itself after <paramref name="lifetime"/> seconds. Returns the spawned blip, or
/// null if it could not be created.
/// </summary>
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;
}
// Already tracking this exact transform? Refresh its countdown instead of stacking a duplicate blip,
// so a fresh ping (interval or manual) just resets the existing target's lifetime.
for (int i = 0; i < _blips.Count; i++)
{
var existing = _blips[i];
if (existing && existing.Target == target)
{
existing.Refresh(lifetime);
return existing;
}
}
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<RadarTarget>();
blip.Init(target, referenceCamera, lifetime);
_blips.Add(blip);
return blip;
}
// LateUpdate so the count reflects any blip that self-destructed this frame, in step with RadarTarget's aim.
private void LateUpdate()
{
// Drop self-destructed blips (Destroy leaves a fake-null entry behind).
for (int i = _blips.Count - 1; i >= 0; i--)
if (!_blips[i]) _blips.RemoveAt(i);
bool visible = _blips.Count > 0 && firstPerson && firstPerson.enabled && InSeekPhase();
// Ramp the shared factor toward shown/hidden with the matching speed, so the whole overlay fades
// (alpha + scale) instead of snapping. A mid-fade visibility flip reverses smoothly.
float target = visible ? 1f : 0f;
float speed = Mathf.Max(0f, visible ? fadeInSpeed : fadeOutSpeed);
_fade = Mathf.MoveTowards(_fade, target, speed * Time.deltaTime);
ApplyFade();
}
// The radar only makes sense during the seek phase (hunters chasing hiders). Blips only spawn then anyway,
// but a blip's lifetime can outlast the phase edge, so gating here hides the overlay the instant Seek ends.
private static bool InSeekPhase()
{
var m = MatchManager.Instance;
return m && m.MatchActive && m.LocalSubPhase == MatchManager.MatchSubPhase.Seek;
}
// Applies the current _fade to the whole overlay: disc scale (hiddenScale..1), disc alpha (baseAlpha*fade),
// and the overlay camera - switched off only once fully faded out, so blips keep ticking their lifetime while
// hidden and no rendering happens for an invisible radar. Blips fade themselves via RadarTarget/CurrentFade.
private void ApplyFade()
{
transform.localScale = _baseScale * Mathf.Lerp(hiddenScale, 1f, _fade);
if (_staticRenderers != null)
{
_mpb ??= new MaterialPropertyBlock();
for (int i = 0; i < _staticRenderers.Length; i++)
{
var r = _staticRenderers[i];
if (!r) continue;
Color c = _staticBaseColors[i];
c.a *= _fade;
r.GetPropertyBlock(_mpb);
_mpb.SetColor(BaseColorId, c);
r.SetPropertyBlock(_mpb);
}
}
bool render = _fade > 0.0001f;
if (radarCamera && radarCamera.enabled != render)
radarCamera.enabled = render;
}
}