55 lines
2.2 KiB
C#
55 lines
2.2 KiB
C#
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using UnityEngine.EventSystems;
|
||
using UnityEngine.InputSystem.UI;
|
||
|
||
namespace BaseGames.UI
|
||
{
|
||
/// <summary>
|
||
/// 多设备 UI 焦点守护:挂在持有 EventSystem 的对象上。
|
||
///
|
||
/// 解决"鼠标点击空白处 → 当前选中被清空 → 切换到键盘/手柄时无选中项可导航"的问题:
|
||
/// 记录最近一个有效选中项;当选中丢失且玩家按下导航/确认键(键盘方向键 / 手柄摇杆·十字键 / Submit)时,
|
||
/// 自动把焦点恢复到上一个仍可交互的选中项。仅在"导航意图"出现时恢复,不与鼠标悬停/点击冲突。
|
||
///
|
||
/// 设备无关:依赖 <see cref="InputSystemUIInputModule"/> 的 move/submit Action,
|
||
/// 因此键盘、Xbox、Switch、PlayStation 手柄(均归一为 Gamepad 布局)统一生效。
|
||
/// </summary>
|
||
[DefaultExecutionOrder(100)]
|
||
public class UISelectionRestorer : MonoBehaviour
|
||
{
|
||
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 (!NavigationIntentThisFrame(es)) return;
|
||
if (_lastSelected == null || !_lastSelected.activeInHierarchy) return;
|
||
|
||
var sel = _lastSelected.GetComponent<Selectable>();
|
||
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;
|
||
}
|
||
}
|
||
}
|