added inventory and stamina system
This commit is contained in:
281
Assets/Scripts/PlayerHUD.cs
Normal file
281
Assets/Scripts/PlayerHUD.cs
Normal file
@@ -0,0 +1,281 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user