使用UniTask实现的补间血条
代码
TweenSlider.cs
using Cysharp.Threading.Tasks;
using System;
using System.Threading;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace T2Project.UI
{
[ExecuteAlways]
public class TweenSlider : MonoBehaviour
{
[SerializeField] private Image tweenFill;
[SerializeField] private Image fill;
[SerializeField] private RectTransform border;
public float minValue = 0;
public float maxValue = 1;
public float tweenSpeed = 5;
public float tweenThreshold = 0.1f;
public Color increaseTweenColor = Color.green;
public Color decreaseTweenColor = Color.white;
public UnityEvent onValueChanged;
private RectTransform tweenFillRect;
private RectTransform fillRect;
private bool statusChanged = false;
private readonly ValueChangeListener<float> valueChangeListener = new();
public float Value
{
get => _value;
set
{
_value = Math.Clamp(value, minValue, maxValue);
valueChangeListener.Value = _value;
}
}
[SerializeField] private float _value = 1;
private void OnValidate()
{
Awake();
}
private void Awake()
{
if (tweenFill == null || fill == null || border == null)
{
return;
}
tweenFillRect = tweenFill.rectTransform;
fillRect = fill.rectTransform;
tweenFillRect.pivot = new(0, 0.5f);
tweenFillRect.anchoredPosition = new(0, 0);
fillRect.pivot = new(0, 0.5f);
fillRect.anchoredPosition = new(0, 0);
}
private void OnEnable()
{
SetValueWithoutNotify(Value);
}
private void Start()
{
if (tweenFill == null || fill == null || border == null)
{
return;
}
valueChangeListener.OnValueChanged += val =>
{
statusChanged = true;
onValueChanged?.Invoke();
};
TweenUpdateAsync(destroyCancellationToken).Forget();
}
private async UniTask TweenUpdateAsync(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
await UniTask.WaitForEndOfFrame(token);
if (statusChanged)
{
statusChanged = false;
await UniTask.Yield(token);
continue;
}
float targetFillWidth = border.rect.size.x * (Value / maxValue - 1);
if (valueChangeListener.Value < valueChangeListener.OldValue)
{
tweenFill.color = decreaseTweenColor;
if (await UpdateFillRect(targetFillWidth)) continue;
if (await UpdateTweenRect(targetFillWidth)) continue;
}
else
{
tweenFill.color = increaseTweenColor;
if (await UpdateTweenRect(targetFillWidth)) continue;
if (await UpdateFillRect(targetFillWidth)) continue;
}
fillRect.sizeDelta = new(targetFillWidth, fillRect.sizeDelta.y);
tweenFillRect.sizeDelta = new(targetFillWidth, tweenFillRect.sizeDelta.y);
}
async UniTask<bool> UpdateFillRect(float targetFillWidth)
{
if (Mathf.Abs(fillRect.sizeDelta.x - targetFillWidth) > tweenThreshold)
{
fillRect.sizeDelta = new(Mathf.Lerp(fillRect.sizeDelta.x, targetFillWidth, tweenSpeed * Time.deltaTime), fillRect.sizeDelta.y);
await UniTask.Yield(token);
return true;
}
return false;
}
async UniTask<bool> UpdateTweenRect(float targetFillWidth)
{
if (Mathf.Abs(tweenFillRect.sizeDelta.x - targetFillWidth) > tweenThreshold)
{
tweenFillRect.sizeDelta = new(Mathf.Lerp(tweenFillRect.sizeDelta.x, targetFillWidth, tweenSpeed * Time.deltaTime), tweenFillRect.sizeDelta.y);
await UniTask.Yield(token);
return true;
}
return false;
}
}
public void SetValueWithoutNotify(float value)
{
_value = Math.Clamp(value, minValue, maxValue);
fillRect.sizeDelta = new(border.rect.size.x * (Value / maxValue - 1), fillRect.sizeDelta.y);
tweenFillRect.sizeDelta = new(border.rect.size.x * (Value / maxValue - 1), tweenFillRect.sizeDelta.y);
}
}
}
TweenSliderEditor.cs
using UnityEditor;
using UnityEngine;
namespace T2Project.UI.Editor
{
[CustomEditor(typeof(TweenSlider))]
public class TweenSliderEditor : UnityEditor.Editor
{
private TweenSlider tweenSlider;
private SerializedProperty tweenFillProp;
private SerializedProperty fillProp;
private SerializedProperty borderProp;
private SerializedProperty minValueProp;
private SerializedProperty maxValueProp;
private SerializedProperty tweenSpeedProp;
private SerializedProperty tweenThresholdProp;
private SerializedProperty increaseTweenColorProp;
private SerializedProperty decreaseTweenColorProp;
private SerializedProperty onValueChangedProp;
private SerializedProperty valueProp;
private readonly ValueChangeListener<float> valueChangeListener = new();
private void OnEnable()
{
tweenSlider = (TweenSlider)target;
tweenFillProp = serializedObject.FindProperty("tweenFill");
fillProp = serializedObject.FindProperty("fill");
borderProp = serializedObject.FindProperty("border");
minValueProp = serializedObject.FindProperty("minValue");
maxValueProp = serializedObject.FindProperty("maxValue");
tweenSpeedProp = serializedObject.FindProperty("tweenSpeed");
tweenThresholdProp = serializedObject.FindProperty("tweenThreshold");
increaseTweenColorProp = serializedObject.FindProperty("increaseTweenColor");
decreaseTweenColorProp = serializedObject.FindProperty("decreaseTweenColor");
onValueChangedProp = serializedObject.FindProperty("onValueChanged");
valueProp = serializedObject.FindProperty("_value");
valueChangeListener.OnValueChanged = val =>
{
tweenSlider.Value = val;
EditorUtility.SetDirty(tweenSlider);
};
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.PropertyField(tweenFillProp);
EditorGUILayout.PropertyField(fillProp);
EditorGUILayout.PropertyField(borderProp);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(minValueProp);
EditorGUILayout.PropertyField(maxValueProp);
EditorGUILayout.Slider(valueProp, minValueProp.floatValue, maxValueProp.floatValue, new GUIContent("Value"));
valueChangeListener.Value = valueProp.floatValue;
EditorGUILayout.Space();
EditorGUILayout.PropertyField(tweenSpeedProp);
EditorGUILayout.PropertyField(tweenThresholdProp);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(increaseTweenColorProp);
EditorGUILayout.PropertyField(decreaseTweenColorProp);
EditorGUILayout.Space();
EditorGUILayout.PropertyField(onValueChangedProp);
serializedObject.ApplyModifiedProperties();
}
}
}
ValueChangeListener
public class ValueChangeListener<T> where T : struct
{
private T oldValue;
private T currentValue;
public Action<T> OnValueChanged;
public T OldValue => oldValue;
public T Value
{
get
{
return currentValue;
}
set
{
if (!EqualityComparer<T>.Default.Equals(currentValue, value))
{
OnValueChanged?.Invoke(value);
oldValue = currentValue;
currentValue = value;
}
}
}
}