307 lines
9.7 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|