Files
Emberwild/Assets/GAME/Script/UI/PlayerStatsUI.cs
T
2026-06-22 16:18:34 +02:00

141 lines
4.4 KiB
C#

using UnityEngine;
using UnityEngine.UI;
using Ashwild.Player;
namespace Ashwild.UI
{
public class PlayerStatsUI : MonoBehaviour
{
[Header("Health Bar")]
[SerializeField] private Image healthBarFill;
[Header("Hunger Bar")]
[SerializeField] private Image hungerBarFill;
[Header("Thirst Bar")]
[SerializeField] private Image thirstBarFill;
[Header("Settings")]
[SerializeField] private float smoothSpeed = 5f;
[SerializeField] private Color damageFlashColor = new Color(1f, 0f, 0f, 0.3f);
[SerializeField] private float flashDuration = 0.3f;
private float displayedHealth;
private float displayedHunger;
private float displayedThirst;
private Image flashOverlay;
private float flashTimer;
private PlayerStats playerStats;
private bool bound;
/// <summary>
/// Waits for the networked local player to spawn before reading its stats.
/// </summary>
private void OnEnable()
{
PlayerEvents.LocalPlayerSpawned += HandleLocalPlayerSpawned;
}
/// <summary>
/// Unsubscribes — mirrors OnEnable exactly.
/// </summary>
private void OnDisable()
{
PlayerEvents.LocalPlayerSpawned -= HandleLocalPlayerSpawned;
}
private void Start()
{
displayedHealth = 1f;
displayedHunger = 1f;
displayedThirst = 1f;
// Set up fill bars — does not need the player.
SetupBar(healthBarFill);
SetupBar(hungerBarFill);
SetupBar(thirstBarFill);
// The player may already exist (late UI init); otherwise we wait for the spawn event.
if (PlayerStats.Instance != null)
BindToStats();
}
/// <summary>
/// Binds to the local player's stats as soon as it spawns on the network.
/// </summary>
private void HandleLocalPlayerSpawned() => BindToStats();
/// <summary>
/// Caches the stats reference and listens for health changes; runs once.
/// </summary>
private void BindToStats()
{
if (bound) return;
playerStats = PlayerStats.Instance;
if (playerStats == null) return;
bound = true;
playerStats.onHealthChanged.AddListener(OnHealthChanged);
}
private void SetupBar(Image bar)
{
if (bar == null) return;
bar.type = Image.Type.Filled;
bar.fillMethod = Image.FillMethod.Horizontal;
bar.fillOrigin = (int)Image.OriginHorizontal.Left;
}
private void Update()
{
if (playerStats == null) return;
// Smooth fill towards actual values
displayedHealth = Mathf.Lerp(displayedHealth, playerStats.HealthNormalized, Time.deltaTime * smoothSpeed);
displayedHunger = Mathf.Lerp(displayedHunger, playerStats.HungerNormalized, Time.deltaTime * smoothSpeed);
displayedThirst = Mathf.Lerp(displayedThirst, playerStats.ThirstNormalized, Time.deltaTime * smoothSpeed);
if (healthBarFill != null)
healthBarFill.fillAmount = displayedHealth;
if (hungerBarFill != null)
hungerBarFill.fillAmount = displayedHunger;
if (thirstBarFill != null)
thirstBarFill.fillAmount = displayedThirst;
// Damage flash
if (flashTimer > 0f)
{
flashTimer -= Time.deltaTime;
if (flashOverlay != null)
{
Color c = damageFlashColor;
c.a = damageFlashColor.a * (flashTimer / flashDuration);
flashOverlay.color = c;
}
}
}
private float previousHealth = -1f;
private void OnHealthChanged(float newHealth)
{
// Flash only when health decreases (damage taken)
float normalized = newHealth / playerStats.MaxHealth;
if (previousHealth >= 0f && normalized < previousHealth)
flashTimer = flashDuration;
previousHealth = normalized;
}
private void OnDestroy()
{
if (playerStats != null)
playerStats.onHealthChanged.RemoveListener(OnHealthChanged);
}
}
}