// Copyright (c) Meta Platforms, Inc. and affiliates.
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
namespace Lofelt.NiceVibrations
{
///
/// Add this bar to an object and link it to a bar (possibly the same object the script is on), and you'll be able to resize the bar object based on a current value, located between a min and max value.
/// See the HealthBar.cs script for a use case
///
public class MMProgressBar : MonoBehaviour
{
/// the possible fill modes
public enum FillModes { LocalScale, FillAmount, Width, Height }
/// the possible directions for the fill (for local scale and fill amount only)
public enum BarDirections { LeftToRight, RightToLeft, UpToDown, DownToUp }
/// the possible timescales the bar can work on
public enum TimeScales { UnscaledTime, Time }
[Header("General Settings")]
/// the local scale or fillamount value to reach when the bar is empty
public float StartValue = 0f;
/// the local scale or fillamount value to reach when the bar is full
public float EndValue = 1f;
/// the direction this bar moves to
public BarDirections BarDirection = BarDirections.LeftToRight;
/// the foreground bar's fill mode
public FillModes FillMode = FillModes.LocalScale;
/// defines whether the bar will work on scaled or unscaled time (whether or not it'll keep moving if time is slowed down for example)
public TimeScales TimeScale = TimeScales.UnscaledTime;
[Header("Foreground Bar Settings")]
/// whether or not the foreground bar should lerp
public bool LerpForegroundBar = true;
/// the speed at which to lerp the foreground bar
public float LerpForegroundBarSpeed = 15f;
[Header("Delayed Bar Settings")]
/// the delay before the delayed bar moves (in seconds)
public float Delay = 1f;
/// whether or not the delayed bar's animation should lerp
public bool LerpDelayedBar = true;
/// the speed at which to lerp the delayed bar
public float LerpDelayedBarSpeed = 15f;
[Header("Bindings")]
/// optional - the ID of the player associated to this bar
public string PlayerID;
/// the delayed bar
public Transform DelayedBar;
/// the main, foreground bar
public Transform ForegroundBar;
[Header("Bump")]
/// whether or not the bar should "bump" when changing value
public bool BumpScaleOnChange = true;
/// whether or not the bar should bump when its value increases
public bool BumpOnIncrease = false;
/// the duration of the bump animation
public float BumpDuration = 0.2f;
/// whether or not the bar should flash when bumping
public bool ChangeColorWhenBumping = true;
/// the color to apply to the bar when bumping
public Color BumpColor = Color.white;
/// the curve to map the bump animation on
public AnimationCurve BumpAnimationCurve = new AnimationCurve(new Keyframe(1, 1), new Keyframe(0.3f, 1.05f), new Keyframe(1, 1));
/// the curve to map the bump animation color animation on
public AnimationCurve BumpColorAnimationCurve = new AnimationCurve(new Keyframe(0, 0), new Keyframe(0.3f, 1f), new Keyframe(1, 0));
/// whether or not the bar is bumping right now
public bool Bumping { get; protected set; }
[Header("Realtime")]
/// whether or not this progress bar should update itself every update (if not, you'll have to update it using the UpdateBar method
public bool AutoUpdating = false;
/// the current progress of the bar
[Range(0f, 1f)]
public float BarProgress;
protected float _targetFill;
protected Vector3 _targetLocalScale = Vector3.one;
protected float _newPercent;
protected float _lastPercent;
protected float _lastUpdateTimestamp;
protected bool _bump = false;
protected Color _initialColor;
protected Vector3 _initialScale;
protected Vector3 _newScale;
protected Image _foregroundImage;
protected Image _delayedImage;
protected bool _initialized;
protected Vector2 _initialFrontBarSize;
///
/// On start we store our image component
///
protected virtual void Start()
{
_initialScale = this.transform.localScale;
if (ForegroundBar != null)
{
_foregroundImage = ForegroundBar.GetComponent();
_initialFrontBarSize = _foregroundImage.rectTransform.sizeDelta;
}
if (DelayedBar != null)
{
_delayedImage = DelayedBar.GetComponent();
}
_initialized = true;
}
///
/// On Update we update our bars
///
protected virtual void Update()
{
AutoUpdate();
UpdateFrontBar();
UpdateDelayedBar();
}
protected virtual void AutoUpdate()
{
if (!AutoUpdating)
{
return;
}
_newPercent = Remap(BarProgress, 0f, 1f, StartValue, EndValue);
_targetFill = _newPercent;
_lastUpdateTimestamp = (TimeScale == TimeScales.Time) ? Time.time : Time.unscaledTime;
}
///
/// Updates the front bar's scale
///
protected virtual void UpdateFrontBar()
{
float currentDeltaTime = (TimeScale == TimeScales.Time) ? Time.deltaTime : Time.unscaledTime;
if (ForegroundBar != null)
{
switch (FillMode)
{
case FillModes.LocalScale:
_targetLocalScale = Vector3.one;
switch (BarDirection)
{
case BarDirections.LeftToRight:
_targetLocalScale.x = _targetFill;
break;
case BarDirections.RightToLeft:
_targetLocalScale.x = 1f - _targetFill;
break;
case BarDirections.DownToUp:
_targetLocalScale.y = _targetFill;
break;
case BarDirections.UpToDown:
_targetLocalScale.y = 1f - _targetFill;
break;
}
if (LerpForegroundBar)
{
_newScale = Vector3.Lerp(ForegroundBar.localScale, _targetLocalScale, currentDeltaTime * LerpForegroundBarSpeed);
}
else
{
_newScale = _targetLocalScale;
}
ForegroundBar.localScale = _newScale;
break;
case FillModes.Width:
if (_foregroundImage == null)
{
return;
}
float newSizeX = Remap(_targetFill, 0f, 1f, 0, _initialFrontBarSize.x);
newSizeX = Mathf.Lerp(_foregroundImage.rectTransform.sizeDelta.x, newSizeX, currentDeltaTime * LerpForegroundBarSpeed);
_foregroundImage.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, newSizeX);
break;
case FillModes.Height:
if (_foregroundImage == null)
{
return;
}
float newSizeY = Remap(_targetFill, 0f, 1f, 0, _initialFrontBarSize.y);
newSizeY = Mathf.Lerp(_foregroundImage.rectTransform.sizeDelta.x, newSizeY, currentDeltaTime * LerpForegroundBarSpeed);
_foregroundImage.rectTransform.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, newSizeY);
break;
case FillModes.FillAmount:
if (_foregroundImage == null)
{
return;
}
if (LerpForegroundBar)
{
_foregroundImage.fillAmount = Mathf.Lerp(_foregroundImage.fillAmount, _targetFill, currentDeltaTime * LerpForegroundBarSpeed);
}
else
{
_foregroundImage.fillAmount = _targetFill;
}
break;
}
}
}
///
/// Updates the delayed bar's scale
///
protected virtual void UpdateDelayedBar()
{
float currentDeltaTime = (TimeScale == TimeScales.Time) ? Time.deltaTime : Time.unscaledDeltaTime;
float currentTime = (TimeScale == TimeScales.Time) ? Time.time : Time.unscaledTime;
if (DelayedBar != null)
{
if (currentTime - _lastUpdateTimestamp > Delay)
{
if (FillMode == FillModes.LocalScale)
{
_targetLocalScale = Vector3.one;
switch (BarDirection)
{
case BarDirections.LeftToRight:
_targetLocalScale.x = _targetFill;
break;
case BarDirections.RightToLeft:
_targetLocalScale.x = 1f - _targetFill;
break;
case BarDirections.DownToUp:
_targetLocalScale.y = _targetFill;
break;
case BarDirections.UpToDown:
_targetLocalScale.y = 1f - _targetFill;
break;
}
if (LerpDelayedBar)
{
_newScale = Vector3.Lerp(DelayedBar.localScale, _targetLocalScale, currentDeltaTime * LerpDelayedBarSpeed);
}
else
{
_newScale = _targetLocalScale;
}
DelayedBar.localScale = _newScale;
}
if ((FillMode == FillModes.FillAmount) && (_delayedImage != null))
{
if (LerpDelayedBar)
{
_delayedImage.fillAmount = Mathf.Lerp(_delayedImage.fillAmount, _targetFill, currentDeltaTime * LerpDelayedBarSpeed);
}
else
{
_delayedImage.fillAmount = _targetFill;
}
}
}
}
}
///
/// Updates the bar's values based on the specified parameters
///
/// Current value.
/// Minimum value.
/// Max value.
public virtual void UpdateBar(float currentValue, float minValue, float maxValue)
{
_newPercent = Remap(currentValue, minValue, maxValue, StartValue, EndValue);
if ((_newPercent != BarProgress) && !Bumping)
{
Bump();
}
BarProgress = _newPercent;
_targetFill = _newPercent;
_lastUpdateTimestamp = (TimeScale == TimeScales.Time) ? Time.time : Time.unscaledTime;
_lastPercent = _newPercent;
}
///
/// Triggers a camera bump
///
public virtual void Bump()
{
if (!BumpScaleOnChange || !_initialized)
{
return;
}
if (!BumpOnIncrease && (_lastPercent < _newPercent))
{
return;
}
if (this.gameObject.activeInHierarchy)
{
StartCoroutine(BumpCoroutine());
}
}
///
/// A coroutine that (usually quickly) changes the scale of the bar
///
/// The coroutine.
protected virtual IEnumerator BumpCoroutine()
{
float journey = 0f;
float currentDeltaTime = (TimeScale == TimeScales.Time) ? Time.deltaTime : Time.unscaledDeltaTime;
Bumping = true;
if (_foregroundImage != null)
{
_initialColor = _foregroundImage.color;
}
while (journey <= BumpDuration)
{
journey = journey + currentDeltaTime;
float percent = Mathf.Clamp01(journey / BumpDuration);
float curvePercent = BumpAnimationCurve.Evaluate(percent);
float colorCurvePercent = BumpColorAnimationCurve.Evaluate(percent);
this.transform.localScale = curvePercent * _initialScale;
if (ChangeColorWhenBumping && (_foregroundImage != null))
{
_foregroundImage.color = Color.Lerp(_initialColor, BumpColor, colorCurvePercent);
}
yield return null;
}
_foregroundImage.color = _initialColor;
Bumping = false;
yield return null;
}
protected virtual float Remap(float x, float A, float B, float C, float D)
{
float remappedValue = C + (x - A) / (B - A) * (D - C);
return remappedValue;
}
}
}