com.alicizax.unity.ui.exten.../Runtime/InputGlyph/Core/InputActionReader.cs

301 lines
9.2 KiB
C#
Raw Normal View History

2026-03-20 16:50:30 +08:00
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
/// <summary>
/// 输入读取工具。
/// 负责运行时输入轮询、单次触发和切换态管理,
/// </summary>
public static class InputActionReader
{
/// <summary>
/// 用于标识一次输入读取上下文。
/// 同一个 Action 在不同 owner 或 key 下会拥有独立的按下状态。
/// </summary>
private readonly struct InputReadKey : IEquatable<InputReadKey>
{
public readonly string ActionName;
public readonly int OwnerId;
public readonly string OwnerKey;
/// <summary>
/// 使用实例 ID 作为拥有者标识,适合 Unity 对象。
/// </summary>
public InputReadKey(string actionName, int ownerId)
{
ActionName = actionName ?? string.Empty;
OwnerId = ownerId;
OwnerKey = string.Empty;
}
/// <summary>
/// 使用字符串作为拥有者标识,适合外部系统或手动传入的 key。
/// </summary>
public InputReadKey(string actionName, string ownerKey)
{
ActionName = actionName ?? string.Empty;
OwnerId = 0;
OwnerKey = ownerKey ?? string.Empty;
}
public bool Equals(InputReadKey other)
{
return OwnerId == other.OwnerId
&& string.Equals(ActionName, other.ActionName, StringComparison.Ordinal)
&& string.Equals(OwnerKey, other.OwnerKey, StringComparison.Ordinal);
}
public override bool Equals(object obj)
{
return obj is InputReadKey other && Equals(other);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = 17;
hashCode = (hashCode * 31) + OwnerId;
hashCode = (hashCode * 31) + StringComparer.Ordinal.GetHashCode(ActionName);
hashCode = (hashCode * 31) + StringComparer.Ordinal.GetHashCode(OwnerKey);
return hashCode;
}
}
}
// 记录“本次按下已消费”的键,用于 Once 语义。
private static readonly HashSet<InputReadKey> PressedKeys = new();
// 记录当前处于开启状态的切换键。
private static readonly HashSet<InputReadKey> ToggledKeys = new();
/// <summary>
/// 直接读取指定 Action 的值。
/// </summary>
public static T ReadValue<T>(string actionName) where T : struct
{
return ResolveAction(actionName).ReadValue<T>();
}
/// <summary>
/// 以 object 形式读取指定 Action 的值。
/// </summary>
public static object ReadValue(string actionName)
{
return ResolveAction(actionName).ReadValueAsObject();
}
/// <summary>
/// 仅在 Action 处于按下状态时读取值。
/// </summary>
public static bool TryReadValue<T>(string actionName, out T value) where T : struct
{
InputAction inputAction = ResolveAction(actionName);
if (inputAction.IsPressed())
{
value = inputAction.ReadValue<T>();
return true;
}
value = default;
return false;
}
/// <summary>
/// 仅在 Action 处于按下状态时以 object 形式读取值。
/// </summary>
public static bool TryReadValue(string actionName, out object value)
{
InputAction inputAction = ResolveAction(actionName);
if (inputAction.IsPressed())
{
value = inputAction.ReadValueAsObject();
return true;
}
value = default;
return false;
}
/// <summary>
/// 只在本次按下的第一帧返回 true并输出当前值。
/// owner 用来隔离不同对象的读取状态。
/// </summary>
public static bool TryReadValueOnce<T>(UnityEngine.Object owner, string actionName, out T value) where T : struct
{
if (owner == null)
{
value = default;
return false;
}
return TryReadValueOnceInternal(new InputReadKey(actionName, owner.GetInstanceID()), actionName, out value);
}
/// <summary>
/// 读取按钮型 Action。
/// 非按钮类型会直接抛出异常,避免误用。
/// </summary>
public static bool ReadButton(string actionName)
{
InputAction inputAction = ResolveAction(actionName);
if (inputAction.type == InputActionType.Button)
{
return Convert.ToBoolean(inputAction.ReadValueAsObject());
}
throw new NotSupportedException("[InputActionReader] The Input Action must be a button type.");
}
/// <summary>
/// 对 Unity 对象做一次性按钮读取。
/// </summary>
public static bool ReadButtonOnce(UnityEngine.Object owner, string actionName)
{
return owner != null && ReadButtonOnce(owner.GetInstanceID(), actionName);
}
/// <summary>
/// 对实例 ID 做一次性按钮读取。
/// </summary>
public static bool ReadButtonOnce(int instanceID, string actionName)
{
return ReadButtonOnceInternal(new InputReadKey(actionName, instanceID), actionName);
}
/// <summary>
/// 对字符串 key 做一次性按钮读取。
/// </summary>
public static bool ReadButtonOnce(string key, string actionName)
{
return ReadButtonOnceInternal(new InputReadKey(actionName, key), actionName);
}
/// <summary>
/// 对 Unity 对象读取按钮切换态。
/// 每次新的按下沿会在开/关之间切换。
/// </summary>
public static bool ReadButtonToggle(UnityEngine.Object owner, string actionName)
{
return owner != null && ReadButtonToggle(owner.GetInstanceID(), actionName);
}
/// <summary>
/// 对实例 ID 读取按钮切换态。
/// </summary>
public static bool ReadButtonToggle(int instanceID, string actionName)
{
return ReadButtonToggleInternal(new InputReadKey(actionName, instanceID), actionName);
}
/// <summary>
/// 对字符串 key 读取按钮切换态。
/// </summary>
public static bool ReadButtonToggle(string key, string actionName)
{
return ReadButtonToggleInternal(new InputReadKey(actionName, key), actionName);
}
/// <summary>
/// 重置指定 key 的切换态。
/// </summary>
public static void ResetToggledButton(string key, string actionName)
{
ToggledKeys.Remove(new InputReadKey(actionName, key));
}
/// <summary>
/// 重置某个 Action 名称下的所有切换态。
/// </summary>
public static void ResetToggledButton(string actionName)
{
if (string.IsNullOrEmpty(actionName) || ToggledKeys.Count == 0)
{
return;
}
InputReadKey[] snapshot = new InputReadKey[ToggledKeys.Count];
ToggledKeys.CopyTo(snapshot);
for (int i = 0; i < snapshot.Length; i++)
{
if (string.Equals(snapshot[i].ActionName, actionName, StringComparison.Ordinal))
{
ToggledKeys.Remove(snapshot[i]);
}
}
}
/// <summary>
/// 清空全部切换态缓存。
/// </summary>
public static void ResetToggledButtons()
{
ToggledKeys.Clear();
}
/// <summary>
/// 解析 Action找不到时立即抛错避免静默失败。
/// </summary>
private static InputAction ResolveAction(string actionName)
{
return InputBindingManager.Action(actionName)
?? throw new InvalidOperationException($"[InputActionReader] Action '{actionName}' is not available.");
}
/// <summary>
/// 内部的单次值读取逻辑。
/// 当按键抬起时,会清理 PressedKeys 中对应状态。
/// </summary>
private static bool TryReadValueOnceInternal<T>(InputReadKey readKey, string actionName, out T value) where T : struct
{
InputAction inputAction = ResolveAction(actionName);
if (inputAction.IsPressed())
{
if (PressedKeys.Add(readKey))
{
value = inputAction.ReadValue<T>();
return true;
}
}
else
{
PressedKeys.Remove(readKey);
}
value = default;
return false;
}
/// <summary>
/// 内部的按钮单次触发逻辑。
/// 只有第一次按下返回 true持续按住不会重复触发。
/// </summary>
private static bool ReadButtonOnceInternal(InputReadKey readKey, string actionName)
{
if (ReadButton(actionName))
{
return PressedKeys.Add(readKey);
}
PressedKeys.Remove(readKey);
return false;
}
/// <summary>
/// 内部的按钮切换逻辑。
/// 基于 Once 触发,在每次新的按下沿时切换状态。
/// </summary>
private static bool ReadButtonToggleInternal(InputReadKey readKey, string actionName)
{
if (ReadButtonOnceInternal(readKey, actionName))
{
if (!ToggledKeys.Add(readKey))
{
ToggledKeys.Remove(readKey);
}
}
return ToggledKeys.Contains(readKey);
}
}