260 lines
11 KiB
C#
260 lines
11 KiB
C#
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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<Transform> _enemies = new List<Transform>();
|
|
private readonly List<Transform> _pickups = new List<Transform>();
|
|
private readonly List<Transform> _npcs = new List<Transform>();
|
|
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<HumanoidEnemy>())
|
|
if (e != null) _enemies.Add(e.transform);
|
|
|
|
_pickups.Clear();
|
|
foreach (var p in FindObjectsOfType<PickupItem>())
|
|
if (p != null) _pickups.Add(p.transform);
|
|
|
|
_npcs.Clear();
|
|
foreach (var n in FindObjectsOfType<DialogueNPC>())
|
|
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 ───────────────────────────────────────────────────
|
|
/// <summary>
|
|
/// Converts a world position to a radar screen position.
|
|
/// Returns false if the point is outside the radar range.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>Draws a filled circle approximated by a square (fast IMGUI).</summary>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>Draws a thin circle outline.</summary>
|
|
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;
|
|
}
|
|
}
|