using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
namespace Kill.UI.Components
{
///
/// 滑动选择器组件
/// 支持:主次刻度 + 只在主刻度显示数字 + 小数精度
///
public class SlideSelector : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
[Header("数值范围")]
public float MinValue = 2.0f; // 最小值
public float MaxValue = 4.0f; // 最大值
public float DefaultValue = 3.0f; // 默认值
public float Step = 0.1f; // 刻度间隔(如 0.1 表示 0.1度)
public int DecimalPlaces = 1; // 小数位数(显示用)
[Header("间距设置")]
public float TickSpacing = 20f; // 刻度间距(像素)
public float NumberOffsetY = -50f; // 数字Y轴偏移
[Header("刻度类型设置")]
public bool ShowNumberOnAllTicks = false; // true=每个刻度都显示数字,false=只在主刻度显示
public float MajorStep = 1.0f; // 主刻度间隔(如 1.0 表示每隔1度显示大刻度)
public float MiddleStep = 0.5f; // 中刻度间隔(如 0.5 表示每隔0.5度显示中刻度,仅在ShowNumberOnAllTicks=false时有效)
[Header("主刻度设置 (如 2, 3, 4)")]
public float MajorTickHeight = 50f; // 主刻度高度
public float MajorTickWidth = 3f; // 主刻度宽度
public Color MajorTickColor = new Color(0.7f, 0.7f, 0.7f); // 主刻度颜色
[Header("中刻度设置 (如 2.5, 3.5)")]
public float MiddleTickHeight = 35f; // 中刻度高度
public float MiddleTickWidth = 2f; // 中刻度宽度
public Color MiddleTickColor = new Color(0.6f, 0.6f, 0.6f); // 中刻度颜色
[Header("小刻度设置 (如 2.1, 2.2)")]
public float MinorTickHeight = 20f; // 小刻度高度
public float MinorTickWidth = 2f; // 小刻度宽度
public Color MinorTickColor = new Color(0.5f, 0.5f, 0.5f); // 小刻度颜色
[Header("数字样式")]
public float NumberSize = 36f; // 数字字体大小
public Color NumberColor = Color.white; // 数字颜色
public Vector2 NumberRectSize = new Vector2(60, 50); // 数字区域大小
[Header("选中效果")]
public Color SelectedTickColor = new Color(0, 1, 1); // 选中刻度颜色(青色)
public Color SelectedNumberColor = new Color(0, 1, 1); // 选中数字颜色
public float SelectedScale = 1.0f; // 选中缩放
public float NormalScale = 1.0f; // 普通缩放
public float SnapDuration = 0.15f; // 吸附动画时长
[Header("组件引用")]
public RectTransform Content; // 内容容器
public RectTransform Viewport; // 可视区域(用于计算中心)
// 事件
public event Action OnValueChanged; // 值改变事件
public event Action OnValueSelected; // 值选中事件
// 运行时数据
private float currentValue;
private bool isDragging = false;
private float contentX = 0f;
private Coroutine snapCoroutine;
// 刻度数据
private class TickData
{
public float Value;
public RectTransform TickRect;
public Image TickImage;
public RectTransform NumberRect;
public Text NumberText;
public bool IsMajor;
}
private List ticks = new List();
///
/// 初始化选择器
///
public void Initialize()
{
ClearContent();
CreateTicks();
SetValue(DefaultValue, false);
}
///
/// 清空内容
///
private void ClearContent()
{
foreach (var tick in ticks)
{
if (tick.TickRect != null) Destroy(tick.TickRect.gameObject);
if (tick.NumberRect != null) Destroy(tick.NumberRect.gameObject);
}
ticks.Clear();
if (Content != null)
{
contentX = 0f;
Content.anchoredPosition = Vector2.zero;
}
}
///
/// 创建刻度
///
private void CreateTicks()
{
if (Content == null)
{
Debug.LogError("[SlideSelector] Content is null!");
return;
}
// 计算总刻度数
int totalSteps = Mathf.RoundToInt((MaxValue - MinValue) / Step);
float totalWidth = totalSteps * TickSpacing;
// 设置内容宽度
float viewportWidth = Viewport != null ? Viewport.rect.width : 800f;
float centerOffset = viewportWidth / 2f;
Content.sizeDelta = new Vector2(totalWidth + centerOffset * 2, Content.sizeDelta.y);
for (int i = 0; i <= totalSteps; i++)
{
float value = MinValue + i * Step;
// 判断刻度类型
float majorRemainder = Mathf.Abs(value % MajorStep);
bool isMajor = majorRemainder < 0.001f || Mathf.Abs(majorRemainder - MajorStep) < 0.001f;
// 只有在非"全部显示数字"模式下才判断中刻度
bool isMiddle = false;
if (!ShowNumberOnAllTicks)
{
float middleRemainder = Mathf.Abs(value % MiddleStep);
isMiddle = !isMajor && (middleRemainder < 0.001f || Mathf.Abs(middleRemainder - MiddleStep) < 0.001f);
}
TickData tick = new TickData
{
Value = value,
IsMajor = isMajor
};
float xPos = centerOffset + i * TickSpacing;
// 确定刻度高宽和颜色
float tickHeight, tickWidth;
Color tickColor;
if (isMajor)
{
tickHeight = MajorTickHeight;
tickWidth = MajorTickWidth;
tickColor = MajorTickColor;
}
else if (isMiddle)
{
tickHeight = MiddleTickHeight;
tickWidth = MiddleTickWidth;
tickColor = MiddleTickColor;
}
else
{
tickHeight = MinorTickHeight;
tickWidth = MinorTickWidth;
tickColor = MinorTickColor;
}
// 创建刻度线
GameObject tickObj = new GameObject($"Tick_{value:F1}");
tickObj.transform.SetParent(Content, false);
tick.TickRect = tickObj.AddComponent();
tick.TickImage = tickObj.AddComponent();
// 底部对齐:pivot 在底部,位置在 y=0(底部)
tick.TickRect.anchorMin = new Vector2(0, 0);
tick.TickRect.anchorMax = new Vector2(0, 0);
tick.TickRect.pivot = new Vector2(0.5f, 0);
tick.TickRect.anchoredPosition = new Vector2(xPos, 0);
tick.TickRect.sizeDelta = new Vector2(tickWidth, tickHeight);
// 刻度颜色
tick.TickImage.color = tickColor;
// 判断是否显示数字
bool shouldShowNumber = ShowNumberOnAllTicks || isMajor;
if (shouldShowNumber)
{
GameObject numObj = new GameObject($"Number_{value:F0}");
numObj.transform.SetParent(Content, false);
tick.NumberRect = numObj.AddComponent();
tick.NumberText = numObj.AddComponent();
// 数字也底部对齐
tick.NumberRect.anchorMin = new Vector2(0, 0);
tick.NumberRect.anchorMax = new Vector2(0, 0);
tick.NumberRect.pivot = new Vector2(0.5f, 0);
tick.NumberRect.anchoredPosition = new Vector2(xPos, NumberOffsetY);
tick.NumberRect.sizeDelta = NumberRectSize;
// 显示数值(整数或小数)
if (ShowNumberOnAllTicks)
{
// 每个刻度都显示数字,根据精度显示
tick.NumberText.text = value.ToString($"F{DecimalPlaces}").TrimEnd('0').TrimEnd('.');
}
else
{
// 只在主刻度显示整数
tick.NumberText.text = Mathf.RoundToInt(value).ToString();
}
tick.NumberText.font = Resources.GetBuiltinResource("LegacyRuntime.ttf");
tick.NumberText.fontSize = Mathf.RoundToInt(NumberSize);
tick.NumberText.alignment = TextAnchor.MiddleCenter;
tick.NumberText.color = NumberColor;
}
ticks.Add(tick);
}
}
public void OnBeginDrag(PointerEventData eventData)
{
isDragging = true;
if (snapCoroutine != null)
{
StopCoroutine(snapCoroutine);
snapCoroutine = null;
}
}
public void OnDrag(PointerEventData eventData)
{
float delta = eventData.delta.x;
contentX += delta;
ApplyContentPosition();
UpdateHighlight();
}
public void OnEndDrag(PointerEventData eventData)
{
isDragging = false;
SnapToNearest();
}
///
/// 应用内容位置
///
private void ApplyContentPosition()
{
if (Content == null) return;
float viewportWidth = Viewport != null ? Viewport.rect.width : 800f;
float centerOffset = viewportWidth / 2f;
int totalSteps = Mathf.RoundToInt((MaxValue - MinValue) / Step);
float totalWidth = totalSteps * TickSpacing;
float maxX = centerOffset;
float minX = -(totalWidth);
contentX = Mathf.Clamp(contentX, minX, maxX);
Content.anchoredPosition = new Vector2(contentX, 0);
}
///
/// 更新高亮效果
///
private void UpdateHighlight()
{
if (Content == null || ticks.Count == 0) return;
float viewportWidth = Viewport != null ? Viewport.rect.width : 800f;
float centerX = -contentX + viewportWidth / 2f;
// 计算当前值(距离中心最近的刻度)
float centerOffset = viewportWidth / 2f;
float relativeX = centerX - centerOffset;
int nearestIndex = Mathf.RoundToInt(relativeX / TickSpacing);
nearestIndex = Mathf.Clamp(nearestIndex, 0, ticks.Count - 1);
float newValue = ticks[nearestIndex].Value;
if (Mathf.Abs(newValue - currentValue) > 0.001f)
{
currentValue = newValue;
OnValueChanged?.Invoke(currentValue);
}
// 更新每个刻度的视觉效果
for (int i = 0; i < ticks.Count; i++)
{
var tick = ticks[i];
float tickX = centerOffset + i * TickSpacing;
bool isSelected = (i == nearestIndex);
// 刻度颜色
if (tick.TickImage != null)
{
if (isSelected)
{
tick.TickImage.color = SelectedTickColor;
}
else
{
tick.TickImage.color = tick.IsMajor ? MajorTickColor : MinorTickColor;
}
}
// 数字样式(只有主刻度有数字)
if (tick.NumberText != null)
{
if (isSelected)
{
tick.NumberText.color = SelectedNumberColor;
tick.NumberRect.localScale = Vector3.one * SelectedScale;
}
else
{
tick.NumberText.color = NumberColor;
tick.NumberRect.localScale = Vector3.one * NormalScale;
}
}
}
}
///
/// 吸附到最近的值
///
private void SnapToNearest()
{
if (snapCoroutine != null)
{
StopCoroutine(snapCoroutine);
}
snapCoroutine = StartCoroutine(SnapCoroutine());
}
private IEnumerator SnapCoroutine()
{
float viewportWidth = Viewport != null ? Viewport.rect.width : 800f;
float centerOffset = viewportWidth / 2f;
// 计算目标位置
int currentIndex = Mathf.RoundToInt((currentValue - MinValue) / Step);
float targetX = -(currentIndex * TickSpacing);
float startX = contentX;
float elapsed = 0f;
while (elapsed < SnapDuration)
{
elapsed += Time.deltaTime;
float t = elapsed / SnapDuration;
t = Mathf.SmoothStep(0, 1, t);
contentX = Mathf.Lerp(startX, targetX, t);
ApplyContentPosition();
UpdateHighlight();
yield return null;
}
contentX = targetX;
ApplyContentPosition();
UpdateHighlight();
OnValueSelected?.Invoke(currentValue);
snapCoroutine = null;
}
///
/// 设置值
///
public void SetValue(float value, bool animate = true)
{
value = Mathf.Clamp(value, MinValue, MaxValue);
// 对齐到步长
value = MinValue + Mathf.Round((value - MinValue) / Step) * Step;
currentValue = value;
float viewportWidth = Viewport != null ? Viewport.rect.width : 800f;
float centerOffset = viewportWidth / 2f;
int currentIndex = Mathf.RoundToInt((currentValue - MinValue) / Step);
float targetX = -(currentIndex * TickSpacing);
if (animate && snapCoroutine != null)
{
StopCoroutine(snapCoroutine);
}
if (animate)
{
snapCoroutine = StartCoroutine(SnapToValueCoroutine(targetX));
}
else
{
contentX = targetX;
ApplyContentPosition();
UpdateHighlight();
}
}
private IEnumerator SnapToValueCoroutine(float targetX)
{
float startX = contentX;
float elapsed = 0f;
while (elapsed < SnapDuration)
{
elapsed += Time.deltaTime;
float t = elapsed / SnapDuration;
t = Mathf.SmoothStep(0, 1, t);
contentX = Mathf.Lerp(startX, targetX, t);
ApplyContentPosition();
UpdateHighlight();
yield return null;
}
contentX = targetX;
ApplyContentPosition();
UpdateHighlight();
OnValueSelected?.Invoke(currentValue);
snapCoroutine = null;
}
///
/// 获取当前值
///
public float GetValue()
{
return currentValue;
}
///
/// 重新初始化(用于运行时修改参数)
///
public void Refresh()
{
Initialize();
}
}
}