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