282 lines
12 KiB
C#
282 lines
12 KiB
C#
using UnityEngine;
|
|
|
|
/// <summary>
|
|
/// Draws the player health and stamina bars.
|
|
/// Attach to the Player GameObject alongside Player.cs
|
|
///
|
|
/// Deliberately ugly. This is a Cruelty Squad game.
|
|
/// </summary>
|
|
public class PlayerHUD : MonoBehaviour
|
|
{
|
|
[Header("Layout")]
|
|
public float barWidth = 220f;
|
|
public float barHeight = 16f;
|
|
public float barSpacing = 10f; // gap between health and stamina bars
|
|
public float edgePadX = 18f;
|
|
public float edgePadY = 18f;
|
|
public AnchorCorner anchor = AnchorCorner.BottomLeft;
|
|
|
|
public enum AnchorCorner { BottomLeft, BottomRight, TopLeft, TopRight }
|
|
|
|
[Header("Health Bar")]
|
|
public Color colHealthFull = new Color(0.15f, 0.80f, 0.25f, 1f);
|
|
public Color colHealthMid = new Color(0.85f, 0.75f, 0.05f, 1f);
|
|
public Color colHealthLow = new Color(0.90f, 0.12f, 0.12f, 1f);
|
|
public float healthMidThreshold = 0.5f;
|
|
public float healthLowThreshold = 0.25f;
|
|
|
|
[Header("Stamina Bar")]
|
|
public Color colStaminaNormal = new Color(0.20f, 0.55f, 0.95f, 1f);
|
|
public Color colStaminaRegen = new Color(0.20f, 0.55f, 0.95f, 0.45f); // dimmed while regenerating
|
|
public Color colStaminaExhaust = new Color(0.60f, 0.18f, 0.18f, 1f); // red flash when tapped out
|
|
|
|
[Header("Shared Style")]
|
|
public Color colBarBackground = new Color(0.06f, 0.06f, 0.06f, 0.90f);
|
|
public Color colBarBorder = new Color(0.28f, 0.28f, 0.28f, 1.00f);
|
|
public Color colLabel = new Color(0.80f, 0.80f, 0.80f, 1.00f);
|
|
public Color colLabelCritical = new Color(1.00f, 0.25f, 0.25f, 1.00f);
|
|
public float borderThickness = 1.5f;
|
|
|
|
[Header("Pulse")]
|
|
[Tooltip("The low-health bar pulses opacity when below the low threshold.")]
|
|
public float pulseSpeed = 3.5f;
|
|
|
|
[Header("Speedometer")]
|
|
public bool showSpeedometer = true;
|
|
public Color colSpeedo = new Color(0.20f, 0.95f, 0.40f, 1f);
|
|
public Color colSpeedoFast = new Color(0.95f, 0.80f, 0.10f, 1f);
|
|
public Color colSpeedoCritical = new Color(0.95f, 0.20f, 0.20f, 1f);
|
|
public float speedoFastThreshold = 60f;
|
|
public float speedoCritThreshold = 75f;
|
|
|
|
// ─── Private ─────────────────────────────────────────────────────
|
|
private Player _player;
|
|
private CharacterController _cc;
|
|
private Texture2D _white;
|
|
|
|
// Smooth display values so bars glide rather than snap
|
|
private float _displayHealth = 1f;
|
|
private float _displayStamina = 1f;
|
|
private const float kSmoothSpeed = 8f;
|
|
|
|
// Speed tracking
|
|
private Vector3 _lastPos;
|
|
private float _displaySpeed;
|
|
|
|
// ─────────────────────────────────────────────────────────────────
|
|
void Start()
|
|
{
|
|
_player = GetComponent<Player>();
|
|
_cc = GetComponent<CharacterController>();
|
|
_white = Texture2D.whiteTexture;
|
|
_lastPos = transform.position;
|
|
|
|
if (_player == null)
|
|
Debug.LogWarning("[PlayerHUD] No Player component found on this GameObject!");
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
if (_player == null) return;
|
|
|
|
// Smooth bar fill values towards actual values
|
|
_displayHealth = Mathf.Lerp(_displayHealth, _player.HealthFraction, Time.deltaTime * kSmoothSpeed);
|
|
_displayStamina = Mathf.Lerp(_displayStamina, _player.StaminaFraction, Time.deltaTime * kSmoothSpeed);
|
|
|
|
// Calculate speed from position delta (horizontal only)
|
|
Vector3 pos = transform.position;
|
|
float rawSpeed = new Vector3(pos.x - _lastPos.x, 0f, pos.z - _lastPos.z).magnitude / Time.deltaTime;
|
|
_displaySpeed = Mathf.Lerp(_displaySpeed, rawSpeed, Time.deltaTime * 10f);
|
|
_lastPos = pos;
|
|
}
|
|
|
|
void OnGUI()
|
|
{
|
|
if (_player == null) return;
|
|
|
|
float totalH = barHeight * 2f + barSpacing + 20f; // label heights included
|
|
float totalW = barWidth;
|
|
|
|
float ox, oy;
|
|
switch (anchor)
|
|
{
|
|
case AnchorCorner.BottomLeft:
|
|
ox = edgePadX;
|
|
oy = Screen.height - totalH - edgePadY;
|
|
break;
|
|
case AnchorCorner.BottomRight:
|
|
ox = Screen.width - totalW - edgePadX;
|
|
oy = Screen.height - totalH - edgePadY;
|
|
break;
|
|
case AnchorCorner.TopLeft:
|
|
ox = edgePadX;
|
|
oy = edgePadY;
|
|
break;
|
|
default: // TopRight
|
|
ox = Screen.width - totalW - edgePadX;
|
|
oy = edgePadY;
|
|
break;
|
|
}
|
|
|
|
float cursor = oy;
|
|
|
|
// ── Health bar ───────────────────────────────────────────────
|
|
cursor = DrawStatBar(
|
|
ox, cursor,
|
|
label: "HEALTH",
|
|
value: _player.health,
|
|
maxValue: _player.maxHealth,
|
|
displayFraction: _displayHealth,
|
|
fillColor: GetHealthColor(),
|
|
pulse: _player.HealthFraction <= healthLowThreshold,
|
|
labelCritical: _player.HealthFraction <= healthLowThreshold
|
|
);
|
|
|
|
cursor += barSpacing;
|
|
|
|
// ── Stamina bar ──────────────────────────────────────────────
|
|
Color staminaColor;
|
|
if (_player.isExhausted)
|
|
staminaColor = colStaminaExhaust;
|
|
else if (!_player.isSprinting && _player.StaminaFraction < 1f)
|
|
staminaColor = colStaminaRegen;
|
|
else
|
|
staminaColor = colStaminaNormal;
|
|
|
|
string staminaLabel = _player.isExhausted ? "STAMINA [EXHAUSTED]" : "STAMINA";
|
|
|
|
DrawStatBar(
|
|
ox, cursor,
|
|
label: staminaLabel,
|
|
value: _player.stamina,
|
|
maxValue: _player.maxStamina,
|
|
displayFraction: _displayStamina,
|
|
fillColor: staminaColor,
|
|
pulse: _player.isExhausted,
|
|
labelCritical: _player.isExhausted
|
|
);
|
|
|
|
// ── Speedometer ──────────────────────────────────────────────
|
|
if (showSpeedometer && _cc != null)
|
|
DrawSpeedometer(ox, oy - 90f);
|
|
}
|
|
|
|
void DrawSpeedometer(float x, float y)
|
|
{
|
|
float speed = _displaySpeed;
|
|
|
|
Color col;
|
|
if (speed >= speedoCritThreshold) col = colSpeedoCritical;
|
|
else if (speed >= speedoFastThreshold) col = colSpeedoFast;
|
|
else col = colSpeedo;
|
|
|
|
float w = 130f;
|
|
float h = 70f;
|
|
|
|
// Dark backing box
|
|
DrawTex(new Rect(x - 6f, y - 6f, w + 12f, h + 12f), new Color(0f, 0f, 0f, 0.55f));
|
|
// Coloured left edge accent
|
|
DrawTex(new Rect(x - 6f, y - 6f, 3f, h + 12f), col);
|
|
|
|
// "SPEED" label
|
|
GUIStyle labelStyle = new GUIStyle();
|
|
labelStyle.fontSize = 10;
|
|
labelStyle.fontStyle = FontStyle.Bold;
|
|
labelStyle.normal.textColor = new Color(col.r, col.g, col.b, 0.75f);
|
|
GUI.Label(new Rect(x, y, w, 16f), "SPEED", labelStyle);
|
|
|
|
// Big number
|
|
GUIStyle numStyle = new GUIStyle();
|
|
numStyle.fontSize = 36;
|
|
numStyle.fontStyle = FontStyle.Bold;
|
|
numStyle.normal.textColor = col;
|
|
GUI.Label(new Rect(x, y + 14f, w, 42f), $"{speed:F1}", numStyle);
|
|
|
|
// Unit
|
|
GUIStyle unitStyle = new GUIStyle();
|
|
unitStyle.fontSize = 10;
|
|
unitStyle.fontStyle = FontStyle.Normal;
|
|
unitStyle.normal.textColor = new Color(col.r, col.g, col.b, 0.55f);
|
|
GUI.Label(new Rect(x, y + 54f, w, 16f), "units / sec", unitStyle);
|
|
}
|
|
|
|
// ─── Bar renderer ─────────────────────────────────────────────────
|
|
/// <summary>Draws one labelled bar. Returns the Y position after the bar.</summary>
|
|
float DrawStatBar(
|
|
float x, float y,
|
|
string label,
|
|
float value, float maxValue,
|
|
float displayFraction,
|
|
Color fillColor,
|
|
bool pulse,
|
|
bool labelCritical)
|
|
{
|
|
float labelH = 14f;
|
|
float innerW = barWidth - borderThickness * 2f;
|
|
float innerH = barHeight - borderThickness * 2f;
|
|
|
|
// ── Label ────────────────────────────────────────────────────
|
|
GUIStyle labelStyle = new GUIStyle();
|
|
labelStyle.fontSize = 10;
|
|
labelStyle.fontStyle = FontStyle.Bold;
|
|
labelStyle.normal.textColor = labelCritical ? colLabelCritical : colLabel;
|
|
|
|
string valueStr = $"{Mathf.CeilToInt(value)} / {Mathf.CeilToInt(maxValue)}";
|
|
GUI.Label(new Rect(x, y, barWidth * 0.6f, labelH), label, labelStyle);
|
|
|
|
GUIStyle valStyle = new GUIStyle(labelStyle);
|
|
valStyle.alignment = TextAnchor.UpperRight;
|
|
valStyle.normal.textColor = labelCritical ? colLabelCritical : colLabel;
|
|
GUI.Label(new Rect(x, y, barWidth, labelH), valueStr, valStyle);
|
|
|
|
float barY = y + labelH + 2f;
|
|
|
|
// ── Background ───────────────────────────────────────────────
|
|
DrawTex(new Rect(x, barY, barWidth, barHeight), colBarBorder);
|
|
DrawTex(new Rect(x + borderThickness, barY + borderThickness, innerW, innerH), colBarBackground);
|
|
|
|
// ── Fill ─────────────────────────────────────────────────────
|
|
float fillW = Mathf.Max(0f, displayFraction * innerW);
|
|
|
|
// Pulse alpha on low health / exhaustion
|
|
Color drawColor = fillColor;
|
|
if (pulse)
|
|
{
|
|
float alpha = Mathf.Lerp(0.35f, 1f, (Mathf.Sin(Time.time * pulseSpeed) + 1f) * 0.5f);
|
|
drawColor = new Color(fillColor.r, fillColor.g, fillColor.b, fillColor.a * alpha);
|
|
}
|
|
|
|
if (fillW > 0f)
|
|
DrawTex(new Rect(x + borderThickness, barY + borderThickness, fillW, innerH), drawColor);
|
|
|
|
// ── Segment tick marks (every 25%) ───────────────────────────
|
|
for (int i = 1; i < 4; i++)
|
|
{
|
|
float tickX = x + borderThickness + innerW * (i / 4f);
|
|
DrawTex(new Rect(tickX - 0.5f, barY + borderThickness, 1f, innerH),
|
|
new Color(0f, 0f, 0f, 0.45f));
|
|
}
|
|
|
|
return barY + barHeight; // bottom of bar
|
|
}
|
|
|
|
// ─── Helpers ─────────────────────────────────────────────────────
|
|
Color GetHealthColor()
|
|
{
|
|
float f = _player.HealthFraction;
|
|
if (f <= healthLowThreshold)
|
|
return colHealthLow;
|
|
if (f <= healthMidThreshold)
|
|
return Color.Lerp(colHealthLow, colHealthMid, (f - healthLowThreshold) / (healthMidThreshold - healthLowThreshold));
|
|
return Color.Lerp(colHealthMid, colHealthFull, (f - healthMidThreshold) / (1f - healthMidThreshold));
|
|
}
|
|
|
|
void DrawTex(Rect r, Color c)
|
|
{
|
|
Color prev = GUI.color;
|
|
GUI.color = c;
|
|
GUI.DrawTexture(r, _white);
|
|
GUI.color = prev;
|
|
}
|
|
}
|