Files
zeling_v2/Assets/_Game/Scripts/UI/Controls/UIButton.cs
2026-06-06 09:00:11 +08:00

143 lines
5.9 KiB
C#

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using BaseGames.Core.Events;
using BaseGames.UI.Theme;
namespace BaseGames.UI
{
/// <summary>按钮视觉变体(驱动从主题派生的 <see cref="ColorBlock"/>)。</summary>
public enum UIButtonVariant { Primary, Secondary, Accent, Danger, Ghost }
/// <summary>
/// 主题化按钮变体 + 交互反馈。薄封装在 uGUI <see cref="Button"/> 之上:
/// - 按变体从 <see cref="UIThemeSO"/> 取基色,派生 normal/highlighted/pressed/disabled 并写入 Button.colors。
/// - 选中/悬停时缩放反馈(走 <see cref="UITween.Scale"/>)。
/// - 可选点击/悬停音效(事件频道驱动,不直接耦合 Audio 程序集)。
///
/// 主题来源:优先 Inspector 直接赋的 <see cref="_theme"/>,否则就近向上找 <see cref="UIThemeApplier"/>。
/// </summary>
[RequireComponent(typeof(Button))]
[DisallowMultipleComponent]
public class UIButton : MonoBehaviour,
ISelectHandler, IDeselectHandler, IPointerEnterHandler, IPointerExitHandler
{
[Header("变体")]
[SerializeField] private UIButtonVariant _variant = UIButtonVariant.Primary;
[Tooltip("主题资产;为空则就近向上查找 UIThemeApplier。")]
[SerializeField] private UIThemeSO _theme;
[Header("反馈")]
[Tooltip("选中/悬停时的缩放反馈。")]
[SerializeField] private bool _scaleFeedback = true;
[SerializeField] private float _selectedScale = 1.06f;
[SerializeField] private float _scaleDuration = 0.08f;
[Header("音效(可选,事件频道驱动)")]
[SerializeField] private VoidEventChannelSO _onClickSfx;
[SerializeField] private VoidEventChannelSO _onHoverSfx;
private Button _button;
private Coroutine _scaleRoutine;
private Vector3 _baseScale = Vector3.one;
private void Awake()
{
_button = GetComponent<Button>();
_baseScale = transform.localScale;
_button.onClick.AddListener(RaiseClickSfx);
}
private void OnEnable()
{
ApplyTheme();
transform.localScale = _baseScale;
}
private void OnDisable()
{
if (_scaleRoutine != null) { StopCoroutine(_scaleRoutine); _scaleRoutine = null; }
transform.localScale = _baseScale;
}
// ── 主题应用 ──────────────────────────────────────────────────────────
/// <summary>按当前变体与主题刷新 Button 配色。</summary>
public void ApplyTheme()
{
var theme = ResolveTheme();
if (theme == null) return;
if (_button == null) _button = GetComponent<Button>();
if (_button == null) return;
Color baseColor = VariantBaseColor(theme, _variant);
var cb = _button.colors;
cb.colorMultiplier = 1f;
cb.fadeDuration = 0.1f;
cb.disabledColor = theme.ButtonDisabled;
if (_variant == UIButtonVariant.Ghost)
{
cb.normalColor = new Color(baseColor.r, baseColor.g, baseColor.b, 0f);
cb.highlightedColor = new Color(baseColor.r, baseColor.g, baseColor.b, 0.25f);
cb.pressedColor = new Color(baseColor.r, baseColor.g, baseColor.b, 0.40f);
cb.selectedColor = cb.highlightedColor;
}
else
{
cb.normalColor = baseColor;
cb.highlightedColor = Color.Lerp(baseColor, Color.white, 0.12f);
cb.pressedColor = Color.Lerp(baseColor, Color.black, 0.15f);
cb.selectedColor = cb.highlightedColor;
}
_button.colors = cb;
}
private static Color VariantBaseColor(UIThemeSO t, UIButtonVariant v) => v switch
{
UIButtonVariant.Primary => t.Primary,
UIButtonVariant.Secondary => t.Secondary,
UIButtonVariant.Accent => t.Accent,
UIButtonVariant.Danger => t.Danger,
UIButtonVariant.Ghost => t.TextPrimary,
_ => t.Primary,
};
private UIThemeSO ResolveTheme()
{
if (_theme != null) return _theme;
var applier = GetComponentInParent<UIThemeApplier>(includeInactive: true);
return applier != null ? applier.Theme : null;
}
// ── 交互反馈 ──────────────────────────────────────────────────────────
public void OnSelect(BaseEventData e) { PlayScale(_selectedScale); RaiseHoverSfx(); }
public void OnDeselect(BaseEventData e) { PlayScale(1f); }
public void OnPointerEnter(PointerEventData e) { PlayScale(_selectedScale); RaiseHoverSfx(); }
public void OnPointerExit(PointerEventData e)
{
// 仍为当前选中项则保持放大
if (EventSystem.current != null && EventSystem.current.currentSelectedGameObject == gameObject) return;
PlayScale(1f);
}
private void PlayScale(float factor)
{
if (!_scaleFeedback || !isActiveAndEnabled) return;
if (_scaleRoutine != null) StopCoroutine(_scaleRoutine);
_scaleRoutine = StartCoroutine(UITween.Scale(transform, _baseScale * factor, _scaleDuration));
}
private void RaiseClickSfx() => _onClickSfx?.Raise();
private void RaiseHoverSfx() => _onHoverSfx?.Raise();
#if UNITY_EDITOR
private void OnValidate()
{
if (Application.isPlaying) return;
_button = GetComponent<Button>();
ApplyTheme();
}
#endif
}
}