141 lines
4.4 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|