This commit is contained in:
陈思海 2025-04-11 17:26:28 +08:00
parent a2f231014b
commit 35a5416cc4
16 changed files with 1055 additions and 0 deletions

8
Editor/Res.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1c397bc7b14cfb8439960f6f74c953ca
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 B

View File

@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 337b039db051cab44819dc51e6af1f43
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

View File

@ -0,0 +1,117 @@
fileFormatVersion: 2
guid: 42b2d97a2cb439b4395c6dca63357d89
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
spriteCustomMetadata:
entries: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

3
Editor/UX.meta Normal file
View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f770d49c712145179c9d1e9d6cd6b141
timeCreated: 1744275046

309
Editor/UX/UXButtonEditor.cs Normal file
View File

@ -0,0 +1,309 @@
// UXButtonEditor.cs
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEditor.AnimatedValues;
using UnityEditor.UI;
using UnityEngine;
using UnityEngine.UI;
[CanEditMultipleObjects]
[CustomEditor(typeof(UXButton), true)]
public class UXButtonEditor : Editor
{
private SerializedProperty m_Interactable;
private SerializedProperty m_Mode;
private SerializedProperty m_OnValueChanged;
private SerializedProperty m_OnClick;
private SerializedProperty m_UXGroup;
private SerializedProperty m_TransitionData;
private SerializedProperty m_ChildTransitions;
private SerializedProperty m_ButtonUISounds;
private bool m_ShowChildTransitions = true;
private Dictionary<int, bool> m_ChildTransitionFoldouts = new Dictionary<int, bool>();
private UXGroup group;
private int m_ButtonMode;
private void OnEnable()
{
m_Interactable = serializedObject.FindProperty("m_Interactable");
m_UXGroup = serializedObject.FindProperty("m_UXGroup");
m_Mode = serializedObject.FindProperty("m_Mode");
m_OnValueChanged = serializedObject.FindProperty("m_OnValueChanged");
m_OnClick = serializedObject.FindProperty("m_OnClick");
m_TransitionData = serializedObject.FindProperty("m_TransitionData");
m_ChildTransitions = serializedObject.FindProperty("m_ChildTransitions");
m_ButtonUISounds = serializedObject.FindProperty("m_ButtonUISounds");
m_ChildTransitionFoldouts.Clear();
group = (UXGroup)m_UXGroup.objectReferenceValue;
m_ButtonMode = m_Mode.enumValueIndex;
}
private void ResetEventProperty(SerializedProperty property)
{
SerializedProperty persistentCalls = property.FindPropertyRelative("m_PersistentCalls");
SerializedProperty calls = persistentCalls.FindPropertyRelative("m_Calls");
calls.arraySize = 0;
property.serializedObject.ApplyModifiedProperties();
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginDisabledGroup(EditorApplication.isPlaying);
EditorGUILayout.PropertyField(m_Mode);
EditorGUI.EndDisabledGroup();
EditorGUILayout.PropertyField(m_Interactable);
GUILayout.Space(1);
DrawSelfTransition();
GUILayout.Space(5);
DrawChildTransitions();
EditorGUILayout.PropertyField(m_ButtonUISounds);
EditorGUILayout.Space();
if (m_Mode.enumValueIndex != m_ButtonMode)
{
if (m_ButtonMode == (int)ButtonModeType.Normal)
{
ResetEventProperty(m_OnValueChanged);
m_UXGroup.objectReferenceValue = null;
}
else
{
ResetEventProperty((m_OnClick));
}
m_ButtonMode = m_Mode.enumValueIndex;
}
if (m_Mode.enumValueIndex == (int)ButtonModeType.Toggle)
{
EditorGUILayout.PropertyField(m_UXGroup);
UXGroup newGroup = (UXGroup)m_UXGroup.objectReferenceValue;
if (newGroup != group)
{
UXButton self = target as UXButton;
if (group != null)
{
group.UnregisterButton(self);
}
group = newGroup;
if (newGroup != null)
{
newGroup.RegisterButton(self);
}
}
EditorGUILayout.Space();
EditorGUILayout.PropertyField(m_OnValueChanged);
}
else
{
EditorGUILayout.Space();
EditorGUILayout.PropertyField(m_OnClick);
}
serializedObject.ApplyModifiedProperties();
}
private void DrawChildTransitions()
{
m_ShowChildTransitions = EditorGUILayout.Foldout(m_ShowChildTransitions, "Child Transitions", true);
if (!m_ShowChildTransitions)
return;
EditorGUI.indentLevel++;
// 列表控制按钮
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Add Child Transition", GUILayout.Width(150)))
{
m_ChildTransitions.arraySize++;
serializedObject.ApplyModifiedProperties();
}
EditorGUILayout.EndHorizontal();
// 遍历列表元素
for (int i = 0; i < m_ChildTransitions.arraySize; i++)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
// 获取当前元素的SerializedProperty
SerializedProperty element = m_ChildTransitions.GetArrayElementAtIndex(i);
// 初始化折叠状态
if (!m_ChildTransitionFoldouts.ContainsKey(i))
m_ChildTransitionFoldouts[i] = true;
// 折叠标题
string elementTitle = $"Child Transition {i}";
if (element.FindPropertyRelative("targetGraphic").objectReferenceValue != null)
elementTitle += $" ({element.FindPropertyRelative("targetGraphic").objectReferenceValue.name})";
m_ChildTransitionFoldouts[i] = EditorGUILayout.Foldout(m_ChildTransitionFoldouts[i], elementTitle, true);
if (m_ChildTransitionFoldouts[i])
{
// 绘制单个TransitionData
DrawTransitionData(element);
// 删除按钮
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
var orginColor = GUI.backgroundColor;
GUI.backgroundColor = Color.red;
if (GUILayout.Button("Remove", GUILayout.Width(80)))
{
m_ChildTransitions.DeleteArrayElementAtIndex(i);
m_ChildTransitionFoldouts.Remove(i);
serializedObject.ApplyModifiedProperties();
break; // 删除后退出当前循环
}
GUI.backgroundColor = orginColor;
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space(5);
}
EditorGUI.indentLevel--;
}
private void DrawTransitionData(SerializedProperty transitionData)
{
SerializedProperty targetGraphic = transitionData.FindPropertyRelative("targetGraphic");
SerializedProperty transition = transitionData.FindPropertyRelative("transition");
SerializedProperty colorBlock = transitionData.FindPropertyRelative("colors");
SerializedProperty spriteState = transitionData.FindPropertyRelative("spriteState");
EditorGUI.indentLevel++;
// 绘制目标图形
EditorGUILayout.PropertyField(targetGraphic);
// 获取当前transition类型
var currentTransition = GetTransition(transition);
// 绘制transition类型
EditorGUILayout.PropertyField(transition);
// 显示警告信息
var graphic = targetGraphic.objectReferenceValue as Graphic;
switch (currentTransition)
{
case Selectable.Transition.ColorTint:
if (graphic == null)
EditorGUILayout.HelpBox("需要Graphic组件来使用颜色过渡", MessageType.Warning);
break;
case Selectable.Transition.SpriteSwap:
if (!(graphic is Image))
EditorGUILayout.HelpBox("需要Image组件来使用精灵切换", MessageType.Warning);
break;
}
// 绘制对应类型的属性
switch (currentTransition)
{
case Selectable.Transition.ColorTint:
CheckAndSetColorDefaults(colorBlock, targetGraphic);
EditorGUILayout.PropertyField(colorBlock);
break;
case Selectable.Transition.SpriteSwap:
EditorGUILayout.PropertyField(spriteState);
break;
}
EditorGUI.indentLevel--;
}
void CheckAndSetColorDefaults(SerializedProperty colorBlock, SerializedProperty targetGraphic)
{
bool isDirty = false;
string[] colorProps = new string[] { "m_NormalColor", "m_HighlightedColor", "m_PressedColor", "m_SelectedColor", "m_DisabledColor" };
foreach (var propName in colorProps)
{
SerializedProperty prop = colorBlock.FindPropertyRelative(propName);
Color color = prop.colorValue;
if (color.r == 0 && color.g == 0 && color.b == 0 && color.a == 0)
{
isDirty = true;
if (prop.name == "m_PressedColor")
{
prop.colorValue = new Color(0.7843137f, 0.7843137f, 0.7843137f, 1.0f);
}
else if (prop.name == "m_DisabledColor")
{
prop.colorValue = new Color(0.7843137f, 0.7843137f, 0.7843137f, 0.5f);
}
else
{
prop.colorValue = Color.white;
}
}
}
SerializedProperty fadeDuration = colorBlock.FindPropertyRelative("m_FadeDuration");
SerializedProperty m_ColorMultiplier = colorBlock.FindPropertyRelative("m_ColorMultiplier");
if (isDirty)
{
m_ColorMultiplier.floatValue = 1f;
fadeDuration.floatValue = 0.1f;
}
var graphic = targetGraphic.objectReferenceValue as Graphic;
if (graphic != null)
{
if (!EditorApplication.isPlaying)
{
Color color = colorBlock.FindPropertyRelative("m_NormalColor").colorValue;
graphic.canvasRenderer.SetColor(color);
}
}
}
private void DrawSelfTransition()
{
EditorGUILayout.LabelField("Main Transition", EditorStyles.boldLabel);
SerializedProperty targetGraphic = m_TransitionData.FindPropertyRelative("targetGraphic");
var graphic = targetGraphic.objectReferenceValue as Graphic;
if (graphic == null)
{
graphic = (target as UXButton).GetComponent<Graphic>();
targetGraphic.objectReferenceValue = graphic;
}
EditorGUI.indentLevel++;
DrawTransitionData(m_TransitionData);
EditorGUI.indentLevel--;
EditorGUILayout.Space();
}
static Selectable.Transition GetTransition(SerializedProperty transition)
{
return (Selectable.Transition)transition.enumValueIndex;
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1a6fb65845fb481293f57b30b1bfcb3b
timeCreated: 1744275051

View File

@ -0,0 +1,29 @@
// using UnityEditor;
//
// #if UNITY_EDITOR
// [CustomEditor(typeof(UXGroup))]
// public class UXGroupEditor : Editor
// {
// private SerializedProperty m_AllowSwitchOff;
// private SerializedProperty m_Buttons;
// private SerializedProperty m_OnSelectedChanged;
//
// private void OnEnable()
// {
// m_AllowSwitchOff = serializedObject.FindProperty("m_AllowSwitchOff");
// m_Buttons = serializedObject.FindProperty("m_Buttons");
// m_OnSelectedChanged = serializedObject.FindProperty("onSelectedChanged");
// }
//
// public override void OnInspectorGUI()
// {
// serializedObject.Update();
//
// EditorGUILayout.PropertyField(m_AllowSwitchOff);
// EditorGUILayout.PropertyField(m_Buttons, true);
// EditorGUILayout.PropertyField(m_OnSelectedChanged);
//
// serializedObject.ApplyModifiedProperties();
// }
// }
// #endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d66e1f78170d455c93d71e71ee8f735a
timeCreated: 1744275087

View File

@ -42,6 +42,11 @@ namespace AlicizaX.UI.RecyclerView
button.onClick.RemoveAllListeners();
button.onClick.AddListener(() => action?.Invoke(data));
}
else if (TryGetComponent(out UXButton uxButton))
{
uxButton.onClick.RemoveAllListeners();
uxButton.onClick.AddListener(() => action?.Invoke(data));
}
}
protected internal void BindChoiceState(bool state)

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d002cbb3729643c7813fdfc55b8beb79
timeCreated: 1744274178

View File

@ -0,0 +1,312 @@
using System;
using System.Collections.Generic;
using AlicizaX;
using AlicizaX.UI.Extension;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.Serialization;
using UnityEngine.UI;
using AudioType = AlicizaX.Audio.Runtime.AudioType;
[Serializable]
public enum ButtonModeType
{
Normal,
Toggle
}
[System.Serializable]
public class TransitionData
{
public Graphic targetGraphic;
public Selectable.Transition transition = Selectable.Transition.ColorTint;
public ColorBlock colors;
public SpriteState spriteState;
}
[Serializable]
public struct ButtonSoundData
{
public ButtonSoundType ButtonSoundType;
public string ButtonUISoundName;
}
internal enum SelectionState
{
/// <summary>
/// The UI object can be selected.
/// </summary>
Normal,
/// <summary>
/// The UI object is highlighted.
/// </summary>
Highlighted,
/// <summary>
/// The UI object is pressed.
/// </summary>
Pressed,
/// <summary>
/// The UI object is selected
/// </summary>
Selected,
/// <summary>
/// The UI object cannot be selected.
/// </summary>
Disabled,
}
[DisallowMultipleComponent]
[RequireComponent(typeof(Graphic))]
public class UXButton : UIBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerEnterHandler, IPointerExitHandler
{
[SerializeField] private bool m_Interactable = true;
[SerializeField] private ButtonModeType m_Mode;
[SerializeField] private UnityEvent m_OnClick = new UnityEvent();
[SerializeField] private TransitionData m_TransitionData = new TransitionData();
[SerializeField] private List<TransitionData> m_ChildTransitions = new List<TransitionData>();
[SerializeField] private UXGroup m_UXGroup;
[SerializeField] private List<ButtonSoundCell> m_ButtonUISounds = new List<ButtonSoundCell>();
private SelectionState m_SelectionState = SelectionState.Normal;
private bool m_ExistUI;
private bool m_IsDown;
private bool m_IsTogSelected;
public bool IsSelected
{
get { return m_IsTogSelected; }
internal set
{
m_IsTogSelected = value;
onValueChanged?.Invoke(m_IsTogSelected);
m_SelectionState = m_IsTogSelected ? SelectionState.Selected : SelectionState.Normal;
UpdateVisualState(m_SelectionState, false);
}
}
public UnityEvent onClick
{
get { return m_OnClick; }
set { m_OnClick = value; }
}
[SerializeField] private UnityEvent<bool> m_OnValueChanged = new UnityEvent<bool>();
public UnityEvent<bool> onValueChanged
{
get { return m_OnValueChanged; }
set { m_OnValueChanged = value; }
}
protected override void Awake()
{
base.Awake();
if (m_Mode == ButtonModeType.Toggle)
{
onValueChanged?.Invoke(IsSelected);
}
}
void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
{
if (!m_Interactable) return;
m_IsDown = true;
m_SelectionState = SelectionState.Pressed;
UpdateVisualState(m_SelectionState, false);
PlayButtonSound(ButtonSoundType.Down);
}
void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
{
if (!m_Interactable) return;
m_IsDown = false;
if (!m_IsTogSelected)
{
m_SelectionState = m_ExistUI ? SelectionState.Normal : SelectionState.Highlighted;
UpdateVisualState(m_SelectionState, false);
}
else
{
m_SelectionState = SelectionState.Selected;
UpdateVisualState(m_SelectionState, false);
}
if (!m_ExistUI)
{
ProcessClick();
}
PlayButtonSound(ButtonSoundType.Up);
}
void IPointerEnterHandler.OnPointerEnter(PointerEventData eventData)
{
if (!m_Interactable || CantTouch()) return;
m_SelectionState = SelectionState.Highlighted;
m_ExistUI = false;
UpdateVisualState(m_SelectionState, false);
PlayButtonSound(ButtonSoundType.Enter);
}
void IPointerExitHandler.OnPointerExit(PointerEventData eventData)
{
if (!m_Interactable) return;
if (m_IsDown)
{
m_ExistUI = true;
return;
}
if (CantTouch())
{
return;
}
m_SelectionState = SelectionState.Normal;
UpdateVisualState(m_SelectionState, false);
PlayButtonSound(ButtonSoundType.Exit);
}
private bool CantTouch()
{
return m_Mode == ButtonModeType.Toggle && m_IsTogSelected;
}
private void ProcessClick()
{
if (m_Mode == ButtonModeType.Normal)
{
onClick?.Invoke();
}
else
{
if (m_UXGroup)
{
m_UXGroup.NotifyButtonClicked(this);
return;
}
IsSelected = !IsSelected;
}
}
private void UpdateVisualState(SelectionState state, bool instant)
{
ProcessTransitionData(m_TransitionData, state, instant);
foreach (var transition in m_ChildTransitions)
{
ProcessTransitionData(transition, state, instant);
}
}
private void ProcessTransitionData(TransitionData transition, SelectionState state, bool instant)
{
if (transition.targetGraphic == null) return;
Color tintColor;
Sprite transitionSprite;
switch (state)
{
case SelectionState.Normal:
tintColor = transition.colors.normalColor;
transitionSprite = null;
break;
case SelectionState.Highlighted:
tintColor = transition.colors.highlightedColor;
transitionSprite = transition.spriteState.highlightedSprite;
break;
case SelectionState.Pressed:
tintColor = transition.colors.pressedColor;
transitionSprite = transition.spriteState.pressedSprite;
break;
case SelectionState.Selected:
tintColor = transition.colors.selectedColor;
transitionSprite = transition.spriteState.selectedSprite;
break;
case SelectionState.Disabled:
tintColor = transition.colors.disabledColor;
transitionSprite = transition.spriteState.disabledSprite;
break;
default:
tintColor = Color.black;
transitionSprite = null;
break;
}
switch (transition.transition)
{
case Selectable.Transition.ColorTint:
StartColorTween(transition, tintColor * transition.colors.colorMultiplier, instant);
break;
case Selectable.Transition.SpriteSwap:
DoSpriteSwap(transition, transitionSprite);
break;
}
}
protected void StartColorTween(TransitionData transitionData, Color targetColor, bool instant)
{
if (Application.isPlaying)
{
transitionData.targetGraphic.CrossFadeColor(targetColor, instant ? 0f : transitionData.colors.fadeDuration, true, true);
}
else
{
transitionData.targetGraphic.canvasRenderer.SetColor(targetColor);
}
}
protected void DoSpriteSwap(TransitionData transitionData, Sprite newSprite)
{
if (transitionData.targetGraphic is Image image)
{
image.overrideSprite = newSprite;
}
else if (transitionData.targetGraphic != null)
{
Log.Error($"Target Graphic must be Image for SpriteSwap. Object: {transitionData.targetGraphic.name}");
}
}
protected void PlayButtonSound(ButtonSoundType buttonSoundType)
{
ButtonSoundCell buttonSound = GetButtonSound(buttonSoundType);
if (buttonSound == null || string.IsNullOrEmpty(buttonSound.ButtonUISoundName))
{
return;
}
GameApp.Audio.Play(AudioType.UISound, buttonSound.ButtonUISoundName, false, GameApp.Audio.UISoundVolume, true);
}
protected ButtonSoundCell GetButtonSound(ButtonSoundType buttonSoundType)
{
foreach (var buttonSound in m_ButtonUISounds)
{
if (buttonSound.ButtonSoundType == buttonSoundType)
{
return buttonSound;
}
}
return null;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d7e92b092d584bb39e5239463f064cbe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 337b039db051cab44819dc51e6af1f43, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,124 @@
// UXGroup.cs
using UnityEngine;
using UnityEngine.Events;
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine.EventSystems;
[DisallowMultipleComponent]
public class UXGroup : UIBehaviour
{
[SerializeField] private bool m_AllowSwitchOff;
[ReadOnly] [SerializeField] private List<UXButton> m_Buttons = new List<UXButton>();
private UXButton currentUXButton = null;
public UnityEvent<UXButton> onSelectedChanged = new UnityEvent<UXButton>();
public bool allowSwitchOff
{
get => m_AllowSwitchOff;
set
{
m_AllowSwitchOff = value;
ValidateGroupState();
}
}
protected override void OnDestroy()
{
base.OnDestroy();
m_Buttons.Clear();
currentUXButton = null;
}
protected override void Awake()
{
base.Awake();
ValidateGroupState();
}
public void RegisterButton(UXButton button)
{
if (!m_Buttons.Contains(button))
{
m_Buttons.Add(button);
if (button.IsSelected)
{
if (currentUXButton != null && currentUXButton != button)
{
currentUXButton.IsSelected = false;
}
currentUXButton = button;
}
ValidateGroupState();
}
}
public void UnregisterButton(UXButton button)
{
if (m_Buttons.Contains(button))
{
m_Buttons.Remove(button);
button.IsSelected = false;
}
}
internal void NotifyButtonClicked(UXButton clickedButton)
{
if (!clickedButton.IsSelected)
{
SetSelectedButton(clickedButton);
}
else
{
if (m_AllowSwitchOff)
SetSelectedButton(null);
else if (currentUXButton != clickedButton)
clickedButton.IsSelected = true;
}
}
private void SetSelectedButton(UXButton targetButton)
{
UXButton previousSelected = currentUXButton;
currentUXButton = null; // 防止递归
foreach (var button in m_Buttons)
{
bool shouldSelect = (button == targetButton);
if (button.IsSelected != shouldSelect)
{
button.IsSelected = shouldSelect;
}
if (shouldSelect) currentUXButton = button;
}
if (previousSelected != currentUXButton)
{
onSelectedChanged.Invoke(currentUXButton);
}
}
private void ValidateGroupState()
{
bool anySelected = m_Buttons.Exists(b => b.IsSelected);
if (!anySelected && m_Buttons.Count > 0 && !m_AllowSwitchOff)
{
SetSelectedButton(m_Buttons[0]);
}
}
public bool AnyOtherSelected(UXButton exclusion)
{
foreach (var button in m_Buttons)
{
if (button != exclusion && button.IsSelected)
return true;
}
return false;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f0492ee9ffe496c9f028b5f28a10308
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 42b2d97a2cb439b4395c6dca63357d89, type: 3}
userData:
assetBundleName:
assetBundleVariant: