using UnityEngine; using System.Collections.Generic; /// /// Draws a circular radar in the top-left corner of the screen. /// Shows enemies (red) and pickups (yellow-green) relative to the player. /// Rotates so "up" on the radar = player's forward direction. /// /// Attach to the Player GameObject alongside Player.cs / PlayerHUD.cs. /// Cruelty Squad aesthetic: deliberately ugly, glitchy-looking. /// public class RadarHUD : MonoBehaviour { // ─── Inspector ──────────────────────────────────────────────────── [Header("Layout")] public float edgePadX = 18f; public float edgePadY = 18f; public float radarRadius = 60f; // visual radius of the radar disc (px) public float worldRange = 40f; // world-space units the radar covers [Header("Scan")] [Tooltip("How often (seconds) to re-scan the scene for enemies/pickups.")] public float scanInterval = 0.25f; [Header("Colours")] public Color colBackground = new Color(0.04f, 0.10f, 0.04f, 0.88f); public Color colRim = new Color(0.18f, 0.70f, 0.22f, 0.90f); public Color colGrid = new Color(0.18f, 0.70f, 0.22f, 0.20f); public Color colForwardTick = new Color(0.18f, 0.70f, 0.22f, 0.80f); public Color colEnemy = new Color(0.95f, 0.12f, 0.12f, 1.00f); public Color colPickup = new Color(0.90f, 0.85f, 0.05f, 1.00f); public Color colNPC = new Color(0.20f, 0.95f, 0.40f, 1.00f); public Color colPlayerDot = new Color(1.00f, 1.00f, 1.00f, 1.00f); [Header("Blip Sizes (px)")] public float enemyBlipSize = 5f; public float pickupBlipSize = 4f; public float npcBlipSize = 4f; public float playerDotSize = 5f; [Header("Pulse")] [Tooltip("Enemies pulse their blip opacity.")] public float blipPulseSpeed = 4f; // ─── Private ────────────────────────────────────────────────────── private Texture2D _white; // Cached lists — refreshed on scanInterval private readonly List _enemies = new List(); private readonly List _pickups = new List(); private readonly List _npcs = new List(); private float _nextScan; // Centre and radius of the drawn disc in screen pixels (set each OnGUI call) private Vector2 _centre; private float _r; // ───────────────────────────────────────────────────────────────── void Start() { _white = Texture2D.whiteTexture; ScanScene(); } void Update() { if (Time.time >= _nextScan) { ScanScene(); _nextScan = Time.time + scanInterval; } } void ScanScene() { _enemies.Clear(); foreach (var e in FindObjectsOfType()) if (e != null) _enemies.Add(e.transform); _pickups.Clear(); foreach (var p in FindObjectsOfType()) if (p != null) _pickups.Add(p.transform); _npcs.Clear(); foreach (var n in FindObjectsOfType()) if (n != null) _npcs.Add(n.transform); } void OnGUI() { _r = radarRadius; // Radar disc centre: top-left corner + padding + radius float cx = edgePadX + _r; float cy = edgePadY + _r; _centre = new Vector2(cx, cy); // ── Background disc ────────────────────────────────────────── DrawDisc(cx, cy, _r, colBackground); // ── Grid rings (2 concentric) ───────────────────────────────── DrawDiscOutline(cx, cy, _r * 0.5f, colGrid, 1f); // ── Crosshair lines (faint) ──────────────────────────────────── Color gridCol = colGrid; DrawTex(new Rect(cx - _r, cy - 0.5f, _r * 2f, 1f), gridCol); DrawTex(new Rect(cx - 0.5f, cy - _r, 1f, _r * 2f), gridCol); // ── Outer rim ──────────────────────────────────────────────── DrawDiscOutline(cx, cy, _r, colRim, 1.5f); // ── Player forward tick ──────────────────────────────────────── // A small notch at the top of the disc = player's forward DrawTex(new Rect(cx - 1.5f, cy - _r + 1f, 3f, 6f), colForwardTick); // ── Blips ───────────────────────────────────────────────────── float yaw = transform.eulerAngles.y; // player yaw (world Y) float enemyPulse = Mathf.Lerp(0.45f, 1f, (Mathf.Sin(Time.time * blipPulseSpeed) + 1f) * 0.5f); // Pickups first (rendered under everything) foreach (var t in _pickups) { if (t == null) continue; Vector2 blipPos; if (WorldToRadar(t.position, yaw, out blipPos)) DrawBlip(blipPos.x, blipPos.y, pickupBlipSize, colPickup, 1f, square: true); } // NPCs — green dots foreach (var t in _npcs) { if (t == null) continue; Vector2 blipPos; if (WorldToRadar(t.position, yaw, out blipPos)) DrawBlip(blipPos.x, blipPos.y, npcBlipSize, colNPC, 1f, square: false); } // Enemies Color enemyDraw = new Color(colEnemy.r, colEnemy.g, colEnemy.b, colEnemy.a * enemyPulse); foreach (var t in _enemies) { if (t == null) continue; Vector2 blipPos; if (WorldToRadar(t.position, yaw, out blipPos)) DrawBlip(blipPos.x, blipPos.y, enemyBlipSize, enemyDraw, 1f, square: false); } // Player dot (always at centre) DrawBlip(cx, cy, playerDotSize, colPlayerDot, 1f, square: false); // ── Label ───────────────────────────────────────────────────── GUIStyle labelStyle = new GUIStyle(); labelStyle.fontSize = 8; labelStyle.fontStyle = FontStyle.Bold; labelStyle.normal.textColor = new Color(colRim.r, colRim.g, colRim.b, 0.65f); GUI.Label(new Rect(cx - _r, cy + _r + 3f, _r * 2f, 12f), "RADAR", labelStyle); } // ─── Conversion ─────────────────────────────────────────────────── /// /// Converts a world position to a radar screen position. /// Returns false if the point is outside the radar range. /// bool WorldToRadar(Vector3 worldPos, float playerYawDeg, out Vector2 screenPos) { Vector3 delta = worldPos - transform.position; float dx = delta.x; float dz = delta.z; // Rotate so player forward = up on radar float rad = playerYawDeg * Mathf.Deg2Rad; float rx = dx * Mathf.Cos(rad) - dz * Mathf.Sin(rad); float ry = dx * Mathf.Sin(rad) + dz * Mathf.Cos(rad); // Normalise to radar pixel space float px = _centre.x + (rx / worldRange) * _r; float py = _centre.y - (ry / worldRange) * _r; // screen Y is flipped // Clamp to disc edge if outside range float dist = new Vector2(rx, ry).magnitude; if (dist > worldRange) { // Show as a dim edge blip float ex = _centre.x + (rx / dist) * (_r - 3f); float ey = _centre.y - (ry / dist) * (_r - 3f); screenPos = new Vector2(ex, ey); return true; // still draw, but at edge } screenPos = new Vector2(px, py); return true; } // ─── Drawing helpers ────────────────────────────────────────────── void DrawBlip(float x, float y, float size, Color col, float alpha, bool square) { Color c = new Color(col.r, col.g, col.b, col.a * alpha); float half = size * 0.5f; if (square) { DrawTex(new Rect(x - half, y - half, size, size), c); } else { // Approximate circle with a 3x3 cross pattern DrawTex(new Rect(x - half, y - half * 0.4f, size, size * 0.4f), c); DrawTex(new Rect(x - half * 0.4f, y - half, size * 0.4f, size), c); } } /// Draws a filled circle approximated by a square (fast IMGUI). void DrawDisc(float cx, float cy, float r, Color col) { // Draw as a stack of horizontal lines to get a rough circle int steps = Mathf.CeilToInt(r * 2f); for (int i = 0; i < steps; i++) { float t = (i / (float)steps) * 2f - 1f; // -1 to 1 float hw = Mathf.Sqrt(Mathf.Max(0f, 1f - t * t)) * r; float y = cy + t * r; DrawTex(new Rect(cx - hw, y, hw * 2f, 1f), col); } } /// Draws a thin circle outline. void DrawDiscOutline(float cx, float cy, float r, Color col, float thickness) { int segments = Mathf.Max(32, Mathf.CeilToInt(r * 3f)); for (int i = 0; i < segments; i++) { float a0 = (i / (float)segments) * Mathf.PI * 2f; float a1 = ((i + 1) / (float)segments) * Mathf.PI * 2f; float x0 = cx + Mathf.Cos(a0) * r; float y0 = cy + Mathf.Sin(a0) * r; float x1 = cx + Mathf.Cos(a1) * r; float y1 = cy + Mathf.Sin(a1) * r; // Draw a tiny rect along each segment float len = Mathf.Sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); if (len < 0.01f) continue; DrawTex(new Rect(Mathf.Min(x0, x1) - thickness * 0.5f, Mathf.Min(y0, y1) - thickness * 0.5f, Mathf.Abs(x1 - x0) + thickness, Mathf.Abs(y1 - y0) + thickness), col); } } void DrawTex(Rect r, Color c) { Color prev = GUI.color; GUI.color = c; GUI.DrawTexture(r, _white); GUI.color = prev; } }