This commit is contained in:
陈思海 2026-03-18 16:01:18 +08:00
parent b50ab115a9
commit 7b4feec0f0
6 changed files with 6 additions and 64 deletions

View File

@ -76,8 +76,7 @@ namespace UnityEngine.UI
{
Press();
PlayAudio(clickAudioClip);
// if we get set disabled during the press
// don't run the coroutine.
if (!IsActive() || !IsInteractable())
return;

View File

@ -21,7 +21,6 @@ namespace UnityEngine.UI
[SerializeField]
private List<UXToggle> m_Toggles = new List<UXToggle>();
// 新增:默认选中的 Toggle可在 Inspector 设置)
[SerializeField]
private UXToggle m_DefaultToggle;
@ -76,7 +75,6 @@ namespace UnityEngine.UI
if (m_Toggles.Contains(toggle))
m_Toggles.Remove(toggle);
// 如果被移除的正好是默认选项,则清空默认项
if (m_DefaultToggle == toggle)
{
m_DefaultToggle = null;
@ -91,32 +89,27 @@ namespace UnityEngine.UI
if (!m_Toggles.Contains(toggle))
m_Toggles.Add(toggle);
// 当组内已有其他开启项,并且不允许 all-off 时,
// 如果新加入的 toggle 本身为 on则需要把它关闭以维持单选。
if (!allowSwitchOff)
{
// 如果已经有一个 active 的 toggle且不是刚加入的这个并且刚加入的 toggle isOn 为 true则关闭刚加入的 toggle
var firstActive = GetFirstActiveToggle();
if (firstActive != null && firstActive != toggle && toggle.isOn)
{
// 我们使用不触发回调的方式避免编辑时产生不必要的事件调用
toggle.SetIsOnWithoutNotify(false);
}
else if (firstActive == null)
{
// 没有任何 active 且不允许 all-off如果 group 指定了 defaultToggle优先选中它但仅当 default 在本组内且可交互)
if (m_DefaultToggle != null && m_Toggles.Contains(m_DefaultToggle))
{
var dt = m_DefaultToggle;
if (dt != null && dt != toggle)
{
// 确保默认项被选中editor/runtime 都适用)
dt.isOn = true;
NotifyToggleOn(dt);
}
else if (dt == toggle)
{
// 新加入项就是默认项,确保它为 on
toggle.isOn = true;
NotifyToggleOn(toggle);
}
@ -125,22 +118,19 @@ namespace UnityEngine.UI
}
}
// 新增:判断组里是否包含某 toggle用于运行时/编辑器避免重复注册)
public bool ContainsToggle(UXToggle toggle)
{
return m_Toggles != null && m_Toggles.Contains(toggle);
}
// Ensure list consistency: clean nulls, and make sure every toggle in this list actually references this group.
public void EnsureValidState()
{
if (m_Toggles == null)
m_Toggles = new List<UXToggle>();
// Remove null references first
m_Toggles.RemoveAll(x => x == null);
// 如果不允许 all-off优先尝试选中 defaultToggle否则选中第一个。
if (!allowSwitchOff && !AnyTogglesOn() && m_Toggles.Count != 0)
{
UXToggle toSelect = null;
@ -164,7 +154,6 @@ namespace UnityEngine.UI
if (activeToggles.Count() > 1)
{
// 如果 defaultToggle 是开启的,优先保留它
UXToggle firstActive = GetFirstActiveToggle();
foreach (UXToggle toggle in activeToggles)
@ -178,8 +167,6 @@ namespace UnityEngine.UI
}
}
// Synchronize each toggle's group reference to this group if necessary,
// but avoid causing re-ordering in cases where it's already consistent.
for (int i = 0; i < m_Toggles.Count; i++)
{
var t = m_Toggles[i];
@ -188,7 +175,6 @@ namespace UnityEngine.UI
if (t.group != this)
{
// 使用 setter 会触发必要的注册/注销逻辑
t.group = this;
}
}
@ -206,7 +192,6 @@ namespace UnityEngine.UI
public UXToggle GetFirstActiveToggle()
{
// 优先返回 defaultToggle如果它处于 on 且在组内)
if (m_DefaultToggle != null && m_Toggles.Contains(m_DefaultToggle) && m_DefaultToggle.isOn)
return m_DefaultToggle;

View File

@ -101,10 +101,9 @@ namespace UnityEngine.UI
base.OnDidApplyAnimationProperties();
}
// Centralized group setter logic.
private void SetToggleGroup(UXGroup newGroup, bool setMemberValue)
{
// 如果组没有改变,仍然需要确保组里包含此 toggle修复编辑器中批量拖拽只注册最后一项的问题
if (m_Group == newGroup)
{
if (setMemberValue)
@ -115,21 +114,18 @@ namespace UnityEngine.UI
newGroup.RegisterToggle(this);
}
// 尝试同步组状态,确保编辑器批量赋值时能稳定显示
if (newGroup != null)
newGroup.EnsureValidState();
return;
}
// 从旧组注销(如果存在)
if (m_Group != null)
m_Group.UnregisterToggle(this);
if (setMemberValue)
m_Group = newGroup;
// 注册到新组(不再强依赖 IsActive(),以保证编辑器批量赋值时也能正确注册)
if (newGroup != null)
{
if (!newGroup.ContainsToggle(this))
@ -137,11 +133,9 @@ namespace UnityEngine.UI
newGroup.RegisterToggle(this);
}
// 如果正在 on通知组维持单选逻辑
if (isOn)
newGroup.NotifyToggleOn(this);
// 同步组的内部状态,确保 Inspector 列表正确显示
newGroup.EnsureValidState();
}
}
@ -157,19 +151,14 @@ namespace UnityEngine.UI
Set(value, false);
}
// 在 UXToggle 类内(建议放在类底部较靠近 Set 和 PlayEffect 的位置)加入:
// 覆盖 DoStateTransition保证 isOn 时视觉上总是 Selected除非 Disabled
protected override void DoStateTransition(Selectable.SelectionState state, bool instant)
{
// 如果被禁用,照常显示 Disabled
if (state == Selectable.SelectionState.Disabled)
{
base.DoStateTransition(state, instant);
return;
}
// 当 isOn 为 true 时,总是以 Selected 的样式渲染(但不改变 EventSystem 的真实 selection
if (m_IsOn)
state = Selectable.SelectionState.Selected;
@ -199,11 +188,8 @@ namespace UnityEngine.UI
onValueChanged.Invoke(m_IsOn);
}
// 触发选择态视觉刷新(当 isOn 为 true 显示 Selectedfalse 时回到 normal/hover/... 的计算结果)
// instant 参数:如果 ToggleTransition 是 None 我们使用 instant为保持和原来 PlayEffect 一致。
bool instant = (toggleTransition == Toggle.ToggleTransition.None);
// 传入的 state当 isOn 为 trueDoStateTransition 会将其替换为 Selected
// 当 isOn 为 false使用 currentSelectionState 以让正常的鼠标/键盘状态生效。
var stateToApply = m_IsOn ? Selectable.SelectionState.Selected : currentSelectionState;
DoStateTransition(stateToApply, instant);
}

View File

@ -25,7 +25,6 @@ namespace UnityEngine.UI
}
}
// 改成 Component[](或 MonoBehaviour[]Unity 可以序列化 Component 引用
[SerializeField] private Component[] hotButtons;
internal void BindHotKeys()

View File

@ -16,7 +16,6 @@ namespace UnityEngine.UI
Performed = 1
}
// 优化1: 使用struct减少GC
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct HotkeyRegistration
{
@ -31,7 +30,6 @@ namespace UnityEngine.UI
}
}
// 优化2: 使用struct存储handler信息
[StructLayout(LayoutKind.Sequential)]
internal struct HandlerInfo
{
@ -47,28 +45,23 @@ namespace UnityEngine.UI
internal static class UXHotkeyRegisterManager
{
// 优化3: 使用数组池替代List避免频繁扩容
private const int INITIAL_CAPACITY = 32;
private const int MAX_REGISTRATIONS_PER_ACTION = 16;
// 优化4: 使用固定大小的数组池
private static HotkeyRegistration[][] _registrationPool;
private static int[] _registrationCounts;
private static string[] _actionIds;
private static int _actionCount;
private static int _actionCapacity;
// 优化5: 使用数组替代Dictionary更快的查找
private static HandlerInfo[] _handlers;
private static InputActionReference[] _actionRefs;
// 优化6: Button到ActionId的映射使用数组索引
private static IHotkeyTrigger[] _buttons;
private static int[] _buttonToActionIndex;
private static int _buttonCount;
private static int _buttonCapacity;
// 优化7: 缓存委托,避免每次创建
private static Action<InputAction.CallbackContext>[] _cachedHandlers;
private static int _cachedHandlerCount;
@ -128,7 +121,6 @@ namespace UnityEngine.UI
string actionId = action.action.id.ToString();
int actionIndex = FindOrCreateActionIndex(actionId);
// 添加注册信息
ref int count = ref _registrationCounts[actionIndex];
if (count >= MAX_REGISTRATIONS_PER_ACTION)
{
@ -139,11 +131,9 @@ namespace UnityEngine.UI
_registrationPool[actionIndex][count] = new HotkeyRegistration(button, pressType);
count++;
// 记录button映射
int buttonIndex = FindOrCreateButtonIndex(button);
_buttonToActionIndex[buttonIndex] = actionIndex;
// 优化8: 只在第一次注册时创建handler
if (count == 1)
{
Action<InputAction.CallbackContext> handler = GetOrCreateHandler(actionIndex);
@ -178,7 +168,6 @@ namespace UnityEngine.UI
if (actionIndex < 0)
return;
// 从注册列表中移除
ref int count = ref _registrationCounts[actionIndex];
HotkeyRegistration[] registrations = _registrationPool[actionIndex];
@ -188,12 +177,10 @@ namespace UnityEngine.UI
{
EHotkeyPressType pressType = registrations[i].pressType;
// 优化9: Swap-remove避免数组移动
registrations[i] = registrations[count - 1];
registrations[count - 1] = default;
count--;
// 如果是最后一个注册清理handler
if (count == 0)
{
ref HandlerInfo handlerInfo = ref _handlers[actionIndex];
@ -223,12 +210,10 @@ namespace UnityEngine.UI
}
}
// 清理button映射
_buttons[buttonIndex] = null;
_buttonToActionIndex[buttonIndex] = -1;
}
// 优化10: 使用缓存的委托,避免闭包分配
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Action<InputAction.CallbackContext> GetOrCreateHandler(int actionIndex)
{
@ -242,7 +227,6 @@ namespace UnityEngine.UI
Array.Resize(ref _cachedHandlers, actionIndex + 1);
}
// 优化11: 使用静态方法 + 参数传递,避免闭包
int capturedIndex = actionIndex;
_cachedHandlers[actionIndex] = ctx => OnHotkeyTriggered(capturedIndex);
return _cachedHandlers[actionIndex];
@ -254,24 +238,20 @@ namespace UnityEngine.UI
int count = _registrationCounts[actionIndex];
if (count > 0)
{
// 触发最后一个注册的button栈顶
ref HotkeyRegistration registration = ref _registrationPool[actionIndex][count - 1];
registration.button?.HotkeyActionTrigger();
}
}
// 优化12: 线性查找小数据集比Dictionary更快
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int FindOrCreateActionIndex(string actionId)
{
// 查找现有
for (int i = 0; i < _actionCount; i++)
{
if (_actionIds[i] == actionId)
return i;
}
// 创建新的
if (_actionCount >= _actionCapacity)
{
ExpandActionCapacity();
@ -285,14 +265,12 @@ namespace UnityEngine.UI
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int FindOrCreateButtonIndex(IHotkeyTrigger button)
{
// 查找现有
for (int i = 0; i < _buttonCount; i++)
{
if (ReferenceEquals(_buttons[i], button))
return i;
}
// 查找空槽
for (int i = 0; i < _buttonCapacity; i++)
{
if (_buttons[i] == null)
@ -304,7 +282,6 @@ namespace UnityEngine.UI
}
}
// 扩容
if (_buttonCount >= _buttonCapacity)
{
ExpandButtonCapacity();
@ -358,7 +335,6 @@ namespace UnityEngine.UI
_buttonCapacity = newCapacity;
}
// 优化13: 批量操作API
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RegisterHotkeyBatch(Span<IHotkeyTrigger> buttons, InputActionReference action, EHotkeyPressType pressType)
{
@ -377,7 +353,6 @@ namespace UnityEngine.UI
}
}
// 调试信息
#if UNITY_EDITOR
public static string GetDebugInfo()
{
@ -411,7 +386,6 @@ public static class UXHotkeyHotkeyExtension
}
}
// 优化14: 批量绑定
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void BindHotKeyBatch(this IHotkeyTrigger[] buttons)
{

View File

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System;
using System.Linq;
namespace UnityEngine.UI
{