Files
2026-07-08 20:42:51 +08:00

111 lines
3.3 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Serialization;
namespace Player
{
[System.Serializable]
public class VitalStat
{
[System.Serializable]
public class FloatEvent : UnityEvent<float> { }
[System.Serializable]
public class ThresholdRule
{
[Range(0f, 1f)] public float normalizedThreshold = 0.5f;
public UnityEvent onEnter = new UnityEvent();
public UnityEvent onExit = new UnityEvent();
[HideInInspector] public bool isBelow;
}
public string id;
public float minValue = 0f;
public float maxValue = 100f;
public float currentValue = 100f;
[FormerlySerializedAs("decayPerSecond")] public float decayPerMinute = 0f;
public FloatEvent onValueChanged = new FloatEvent();
public FloatEvent onNormalizedChanged = new FloatEvent();
public List<ThresholdRule> thresholds = new List<ThresholdRule>();
public float Normalized
{
get
{
if (Mathf.Approximately(minValue, maxValue)) return 0f;
return Mathf.InverseLerp(minValue, maxValue, currentValue);
}
}
public void Initialize(bool invokeEvents = true)
{
currentValue = Mathf.Clamp(currentValue, minValue, maxValue);
UpdateThresholdStates(invokeEvents: false);
if (invokeEvents) InvokeAll();
}
public void Tick(float deltaTime)
{
if (decayPerMinute == 0f) return;
Add(-(decayPerMinute / 60f) * deltaTime);
}
public void Add(float delta)
{
SetCurrent(currentValue + delta);
}
public void SetCurrent(float value)
{
float clamped = Mathf.Clamp(value, minValue, maxValue);
if (Mathf.Approximately(clamped, currentValue)) return;
currentValue = clamped;
InvokeAll();
UpdateThresholdStates(invokeEvents: true);
}
public void SetRange(float min, float max, bool preserveNormalized = true)
{
float normalized = Normalized;
minValue = min;
maxValue = max;
if (preserveNormalized)
{
currentValue = Mathf.Lerp(minValue, maxValue, normalized);
}
else
{
currentValue = Mathf.Clamp(currentValue, minValue, maxValue);
}
InvokeAll();
UpdateThresholdStates(invokeEvents: true);
}
void InvokeAll()
{
onValueChanged.Invoke(currentValue);
onNormalizedChanged.Invoke(Normalized);
}
void UpdateThresholdStates(bool invokeEvents)
{
float normalized = Normalized;
for (int i = 0; i < thresholds.Count; i++)
{
ThresholdRule rule = thresholds[i];
bool below = normalized <= rule.normalizedThreshold;
if (invokeEvents)
{
if (!rule.isBelow && below) rule.onEnter.Invoke();
else if (rule.isBelow && !below) rule.onExit.Invoke();
}
rule.isBelow = below;
}
}
}
}