318 lines
9.8 KiB
C#
318 lines
9.8 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace AibisDream.UI
|
|
{
|
|
public class SpriteProgressBar : MonoBehaviour
|
|
{
|
|
[Header("进度条设置")]
|
|
[SerializeField] private int spriteRendererCount = 10;
|
|
[SerializeField] private int maxValue = 100;
|
|
[SerializeField] private int currentValue = 0;
|
|
|
|
[Header("显示设置")]
|
|
[SerializeField] private Sprite sprite;
|
|
[SerializeField] private float spacing = 1f;
|
|
[SerializeField] private Vector3 startPosition = Vector3.zero;
|
|
[SerializeField] private bool horizontal = true;
|
|
|
|
[Header("渲染层级设置")]
|
|
[CustomSortingLayer]
|
|
[SerializeField] private int sortingLayerID = 0;
|
|
[SerializeField] private int sortingOrder = 0;
|
|
|
|
private UnityEvent<int> onValueChanged = new UnityEvent<int>();
|
|
|
|
/// <summary>
|
|
/// 进度条值变化事件
|
|
/// </summary>
|
|
public UnityEvent<int> OnValueChanged => onValueChanged;
|
|
|
|
private readonly List<SpriteRenderer> _spriteRenderers = new List<SpriteRenderer>();
|
|
private bool _isInitialized = false;
|
|
#if UNITY_EDITOR
|
|
private bool _editorUpdateScheduled = false;
|
|
#endif
|
|
|
|
public int CurrentValue
|
|
{
|
|
get => currentValue;
|
|
set
|
|
{
|
|
if (currentValue == value)
|
|
return;
|
|
|
|
// 允许超出范围,不进行限制
|
|
currentValue = value;
|
|
UpdateProgress();
|
|
}
|
|
}
|
|
|
|
public int MaxValue
|
|
{
|
|
get => maxValue;
|
|
set
|
|
{
|
|
if (maxValue == value)
|
|
return;
|
|
|
|
maxValue = Mathf.Max(1, value);
|
|
// 不再自动限制 currentValue,允许超出范围
|
|
|
|
UpdateProgress();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取用于界面显示的值(限制在 [0, MaxValue] 范围内)
|
|
/// </summary>
|
|
public int DisplayValue => Mathf.Clamp(currentValue, 0, maxValue);
|
|
|
|
public float SpriteCount => spriteRendererCount;
|
|
|
|
/// <summary>
|
|
/// 获取所有 SpriteRenderer 的只读列表
|
|
/// </summary>
|
|
public IReadOnlyList<SpriteRenderer> SpriteRenderers => _spriteRenderers;
|
|
|
|
private void Awake()
|
|
{
|
|
#if UNITY_EDITOR
|
|
// 运行时启动时,清理编辑器模式下创建的子对象
|
|
if (Application.isPlaying)
|
|
{
|
|
ClearEditorCreatedChildren();
|
|
}
|
|
#endif
|
|
if (!_isInitialized)
|
|
{
|
|
Initialize();
|
|
}
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
if (!_isInitialized)
|
|
{
|
|
Initialize();
|
|
}
|
|
UpdateProgress();
|
|
}
|
|
|
|
private void Initialize()
|
|
{
|
|
if (sprite == null)
|
|
{
|
|
Debug.LogWarning($"[SpriteProgressBar] {gameObject.name}: Sprite 未设置,无法创建进度条");
|
|
return;
|
|
}
|
|
|
|
ClearSpriteRenderers();
|
|
CreateSpriteRenderers();
|
|
_isInitialized = true;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
/// <summary>
|
|
/// 清理编辑器模式下创建的子对象(仅在运行时调用)
|
|
/// </summary>
|
|
private void ClearEditorCreatedChildren()
|
|
{
|
|
// 清理所有子对象,避免编辑器模式下创建的对象与运行时对象冲突
|
|
while (transform.childCount > 0)
|
|
{
|
|
var child = transform.GetChild(0);
|
|
if (Application.isPlaying)
|
|
{
|
|
Destroy(child.gameObject);
|
|
}
|
|
else
|
|
{
|
|
DestroyImmediate(child.gameObject);
|
|
}
|
|
}
|
|
_spriteRenderers.Clear();
|
|
_isInitialized = false;
|
|
}
|
|
#endif
|
|
|
|
private void CreateSpriteRenderers()
|
|
{
|
|
if (sprite == null) return;
|
|
|
|
for (int i = 0; i < spriteRendererCount; i++)
|
|
{
|
|
GameObject spriteObj = new GameObject($"ProgressItem_{i + 1}");
|
|
spriteObj.transform.SetParent(transform);
|
|
|
|
#if UNITY_EDITOR
|
|
// 编辑器模式下创建的对象不保存到场景
|
|
if (!Application.isPlaying)
|
|
{
|
|
spriteObj.hideFlags = HideFlags.DontSaveInBuild | HideFlags.DontSaveInEditor;
|
|
}
|
|
#endif
|
|
|
|
// 计算位置
|
|
Vector3 position = startPosition;
|
|
if (horizontal)
|
|
{
|
|
position.x += i * spacing;
|
|
}
|
|
else
|
|
{
|
|
position.y += i * spacing;
|
|
}
|
|
spriteObj.transform.localPosition = position;
|
|
|
|
SpriteRenderer sr = spriteObj.AddComponent<SpriteRenderer>();
|
|
sr.sprite = sprite;
|
|
sr.sortingLayerID = sortingLayerID;
|
|
sr.sortingOrder = sortingOrder;
|
|
|
|
_spriteRenderers.Add(sr);
|
|
}
|
|
}
|
|
|
|
private void ClearSpriteRenderers()
|
|
{
|
|
#if UNITY_EDITOR
|
|
// 在编辑器中,需要特殊处理
|
|
if (!Application.isPlaying)
|
|
{
|
|
// 清理子对象
|
|
while (transform.childCount > 0)
|
|
{
|
|
DestroyImmediate(transform.GetChild(0).gameObject);
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
// 运行时清理
|
|
foreach (var sr in _spriteRenderers)
|
|
{
|
|
if (sr != null && sr.gameObject != null)
|
|
{
|
|
Destroy(sr.gameObject);
|
|
}
|
|
}
|
|
// 同时清理可能残留的子对象
|
|
while (transform.childCount > 0)
|
|
{
|
|
var child = transform.GetChild(0);
|
|
Destroy(child.gameObject);
|
|
}
|
|
}
|
|
|
|
_spriteRenderers.Clear();
|
|
}
|
|
|
|
private void UpdateProgress()
|
|
{
|
|
if (_spriteRenderers.Count == 0 || maxValue <= 0)
|
|
return;
|
|
|
|
// 使用限制后的值来计算界面显示(不影响实际存储的值)
|
|
int displayValue = DisplayValue;
|
|
|
|
// 计算应该显示的 SpriteRenderer 数量(向上取整)
|
|
float normalizedValue = (float)displayValue / maxValue;
|
|
int activeCount = Mathf.CeilToInt(normalizedValue * spriteRendererCount);
|
|
activeCount = Mathf.Clamp(activeCount, 0, spriteRendererCount);
|
|
|
|
// 更新显示状态和排序层级
|
|
for (int i = 0; i < _spriteRenderers.Count; i++)
|
|
{
|
|
if (_spriteRenderers[i] != null)
|
|
{
|
|
_spriteRenderers[i].gameObject.SetActive(i < activeCount);
|
|
// 确保排序层级始终是最新的
|
|
_spriteRenderers[i].sortingLayerID = sortingLayerID;
|
|
_spriteRenderers[i].sortingOrder = sortingOrder;
|
|
}
|
|
}
|
|
|
|
// 触发值变化事件
|
|
onValueChanged?.Invoke(currentValue);
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
private void OnValidate()
|
|
{
|
|
// 确保值有效(但不限制 currentValue,允许超出范围)
|
|
spriteRendererCount = Mathf.Max(1, spriteRendererCount);
|
|
maxValue = Mathf.Max(1, maxValue);
|
|
// currentValue 不再限制,允许超出 [0, maxValue] 范围
|
|
spacing = Mathf.Max(0, spacing);
|
|
|
|
// 如果不在播放模式,在编辑器中预览
|
|
if (!Application.isPlaying)
|
|
{
|
|
// 如果 Sprite 已设置,在编辑器中预览
|
|
if (sprite != null)
|
|
{
|
|
// 延迟执行,避免在序列化过程中出现问题
|
|
if (!_editorUpdateScheduled)
|
|
{
|
|
_editorUpdateScheduled = true;
|
|
EditorApplication.delayCall += OnEditorUpdate;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 如果没有 Sprite,清理现有的渲染器
|
|
if (_spriteRenderers.Count > 0)
|
|
{
|
|
ClearSpriteRenderers();
|
|
_isInitialized = false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// 运行时如果排序层级改变,更新所有 SpriteRenderer
|
|
if (_spriteRenderers.Count > 0)
|
|
{
|
|
UpdateSortingLayers();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnEditorUpdate()
|
|
{
|
|
_editorUpdateScheduled = false;
|
|
|
|
if (this == null || gameObject == null)
|
|
return;
|
|
|
|
// 移除延迟调用
|
|
EditorApplication.delayCall -= OnEditorUpdate;
|
|
|
|
if (!Application.isPlaying && sprite != null)
|
|
{
|
|
_isInitialized = false;
|
|
Initialize();
|
|
UpdateProgress();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
private void UpdateSortingLayers()
|
|
{
|
|
foreach (var sr in _spriteRenderers)
|
|
{
|
|
if (sr != null)
|
|
{
|
|
sr.sortingLayerID = sortingLayerID;
|
|
sr.sortingOrder = sortingOrder;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|