Files

307 lines
9.7 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;
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 (!_isInitialized)
{
Initialize();
}
}
private void Start()
{
if (!_isInitialized)
{
Initialize();
}
UpdateProgress();
}
private void Initialize()
{
if (sprite == null)
{
Debug.LogWarning($"[SpriteProgressBar] {gameObject.name}: Sprite 未设置,无法创建进度条");
return;
}
// 只在运行时创建实体对象,编辑器模式下使用 Gizmos 预览
if (!Application.isPlaying)
return;
ClearSpriteRenderers();
CreateSpriteRenderers();
_isInitialized = true;
}
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);
// 只在运行时更新排序层级,编辑器模式下使用 Gizmos 预览
if (Application.isPlaying && _spriteRenderers.Count > 0)
{
UpdateSortingLayers();
}
}
/// <summary>
/// 在 Scene 视图中绘制进度条预览(仅在编辑器模式下)
/// </summary>
private void OnDrawGizmosSelected()
{
if (Application.isPlaying || sprite == null || spriteRendererCount <= 0)
return;
// 计算应该显示的数量
int displayValue = DisplayValue;
float normalizedValue = (float)displayValue / maxValue;
int activeCount = Mathf.CeilToInt(normalizedValue * spriteRendererCount);
activeCount = Mathf.Clamp(activeCount, 0, spriteRendererCount);
// 获取 Sprite 的世界尺寸
Vector3 spriteSize = Vector3.one * 0.5f;
if (sprite != null)
{
spriteSize = new Vector3(
sprite.bounds.size.x,
sprite.bounds.size.y,
0.1f
);
}
Gizmos.matrix = transform.localToWorldMatrix;
for (int i = 0; i < spriteRendererCount; i++)
{
// 计算位置
Vector3 position = startPosition;
if (horizontal)
{
position.x += i * spacing;
}
else
{
position.y += i * spacing;
}
bool isActive = i < activeCount;
// 绘制边框
Gizmos.color = isActive ? Color.green : Color.gray;
Gizmos.DrawWireCube(position, spriteSize * 0.9f);
// 激活的绘制填充
if (isActive)
{
Gizmos.color = new Color(0, 1, 0, 0.3f);
Gizmos.DrawCube(position, spriteSize * 0.8f);
}
}
// 绘制进度条标题
#if UNITY_EDITOR
UnityEditor.Handles.Label(
transform.TransformPoint(startPosition) + Vector3.up * 0.5f,
$"Progress: {currentValue}/{maxValue} ({activeCount}/{spriteRendererCount})"
);
#endif
}
#endif
private void UpdateSortingLayers()
{
foreach (var sr in _spriteRenderers)
{
if (sr != null)
{
sr.sortingLayerID = sortingLayerID;
sr.sortingOrder = sortingOrder;
}
}
}
}
}