using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using UnityEngine.InputSystem.UI; namespace BaseGames.UI { /// /// 多设备 UI 焦点守护:挂在持有 EventSystem 的对象上。 /// /// 解决"鼠标点击空白处 → 当前选中被清空 → 切换到键盘/手柄时无选中项可导航"的问题: /// 记录最近一个有效选中项;当选中丢失且玩家按下导航/确认键(键盘方向键 / 手柄摇杆·十字键 / Submit)时, /// 自动把焦点恢复到上一个仍可交互的选中项。仅在"导航意图"出现时恢复,不与鼠标悬停/点击冲突。 /// /// 设备无关:依赖 的 move/submit Action, /// 因此键盘、Xbox、Switch、PlayStation 手柄(均归一为 Gamepad 布局)统一生效。 /// [DefaultExecutionOrder(100)] public class UISelectionRestorer : MonoBehaviour { [Tooltip("始终保持选中:选中丢失(如鼠标点击空白处)即立即恢复,无需等待导航键。\n" + "适合\"菜单始终有一项高亮\"的手感;关闭则仅在按下方向/确认键时恢复(避免抢占鼠标)。")] [SerializeField] private bool _keepSelectionAlways = true; private GameObject _lastSelected; private void Update() { var es = EventSystem.current; if (es == null) return; var current = es.currentSelectedGameObject; if (current != null && current.activeInHierarchy) { _lastSelected = current; // 记录最近的有效选中 return; } // 选中已丢失:始终保持模式下立即恢复;否则仅在出现导航/确认意图时恢复 if (!_keepSelectionAlways && !NavigationIntentThisFrame(es)) return; if (_lastSelected == null || !_lastSelected.activeInHierarchy) return; var sel = _lastSelected.GetComponent(); if (sel == null || !sel.IsInteractable()) return; es.SetSelectedGameObject(_lastSelected); } private static bool NavigationIntentThisFrame(EventSystem es) { if (es.currentInputModule is not InputSystemUIInputModule m) return false; if (m.move != null && m.move.action != null && m.move.action.WasPerformedThisFrame()) return true; if (m.submit != null && m.submit.action != null && m.submit.action.WasPerformedThisFrame()) return true; return false; } } }