using System; using Unity.VisualScripting; using UnityEngine; #if PACKAGE_INPUT_SYSTEM_EXISTS using UnityEngine.InputSystem; namespace Unity.VisualScripting.InputSystem { public enum InputActionChangeOption { OnPressed, OnHold, OnReleased, } public enum OutputType { Button, Float, Vector2 } /// /// A configurable event to handle input system events. /// [UnitCategory("Events/Input")] public abstract class OnInputSystemEvent : MachineEventUnit { new class Data : EventUnit.Data { internal InputAction Action; } public override IGraphElementData CreateData() { return new Data(); } protected override string hookName => UnityEngine.InputSystem.InputSystem.settings.updateMode == InputSettings.UpdateMode.ProcessEventsInDynamicUpdate ? EventHooks.Update : EventHooks.FixedUpdate; protected abstract OutputType OutputType { get; } [Serialize, Inspectable, UnitHeaderInspectable] public InputActionChangeOption InputActionChangeType; /// /// The InputAction. If it's selected from the dropdown populated from the target PlayerInput, the widget will /// serialize a fake PlayerAction with the right ID/name, but it will need to be compared to the actual action /// fetched at runtime from the playerInput's action asset as the fake one won't trigger any event /// [DoNotSerialize] public ValueInput InputAction { get; private set; } /// /// The target PlayerInput component. if null, the InputAction is expected to be fetched dynamically /// [DoNotSerialize] [PortLabelHidden] [NullMeansSelf] public ValueInput Target { get; private set; } [PortLabelHidden] public ValueOutput FloatValue { get; private set; } [PortLabelHidden] public ValueOutput Vector2Value { get; private set; } #if !PACKAGE_INPUT_SYSTEM_1_2_0_OR_NEWER_EXISTS private bool m_WasRunning; #endif /// /// Stores the last value, and returned by the output port. This intermediary value is there to enable /// "fetching" from the value port, which happens when the value is read, but the node has never been executed. /// Typical use case is "on some other event, read the value of the axis/joystick even if it was never used" /// private Vector2 m_Value; protected override void Definition() { base.Definition(); Target = ValueInput(typeof(PlayerInput), nameof(Target)); Target.SetDefaultValue(null); Target.NullMeansSelf(); InputAction = ValueInput(typeof(InputAction), nameof(InputAction)); InputAction.SetDefaultValue(default(InputAction)); switch (OutputType) { case OutputType.Button: break; case OutputType.Float: // the getValue delegate is what enables fetching FloatValue = ValueOutput(nameof(FloatValue), _ => m_Value.x); break; case OutputType.Vector2: // the getValue delegate is what enables fetching Vector2Value = ValueOutput(nameof(Vector2Value), _ => m_Value); break; default: throw new ArgumentOutOfRangeException(); } } public override void StartListening(GraphStack stack) { base.StartListening(stack); var graphReference = stack.ToReference(); var pi = Flow.FetchValue(Target, graphReference); var inputAction = Flow.FetchValue(InputAction, graphReference); if (inputAction == null) return; var data = stack.GetElementData(this); data.Action = pi ? pi.actions.FindAction(inputAction.id) : inputAction.actionMap != null ? inputAction : null; } public override void StopListening(GraphStack stack) { base.StopListening(stack); var data = stack.GetElementData(this); data.Action = null; } protected override bool ShouldTrigger(Flow flow, EmptyEventArgs args) { var data = flow.stack.GetElementData(this); if (data.Action == null) return false; bool shouldTrigger; #if PACKAGE_INPUT_SYSTEM_1_2_0_OR_NEWER_EXISTS switch (InputActionChangeType) { case InputActionChangeOption.OnPressed: shouldTrigger = data.Action.WasPressedThisFrame(); break; case InputActionChangeOption.OnHold: #if PACKAGE_INPUT_SYSTEM_1_4_0_OR_NEWER_EXISTS shouldTrigger = OutputType == OutputType.Vector2 ? data.Action.IsInProgress() : data.Action.IsPressed(); #else shouldTrigger = OutputType == OutputType.Vector2 ? data.Action.ReadValue().magnitude > Mathf.Epsilon : data.Action.IsPressed(); #endif break; case InputActionChangeOption.OnReleased: shouldTrigger = data.Action.WasReleasedThisFrame(); break; default: throw new ArgumentOutOfRangeException(); } #else // "Started" is true while the button is held, triggered is true only one frame. hence what looks like a bug but isn't switch (InputActionChangeType) { case InputActionChangeOption.OnPressed: shouldTrigger = m_Action.triggered; // started is true too long break; case InputActionChangeOption.OnHold: shouldTrigger = m_Action.phase == InputActionPhase.Started; // triggered is only true one frame break; case InputActionChangeOption.OnReleased: shouldTrigger = m_WasRunning && m_Action.phase != InputActionPhase.Started; // never equal to InputActionPhase.Cancelled when polling break; default: throw new ArgumentOutOfRangeException(); } // Hack - can't make sense of the action phase when polled (always Started or Waiting). Fallback on "== Started" m_WasRunning = m_Action.phase == InputActionPhase.Started; #endif DoAssignArguments(flow, data); return shouldTrigger; } private void DoAssignArguments(Flow flow, Data data) { switch (OutputType) { case OutputType.Button: break; case OutputType.Float: var readValue = data.Action.ReadValue(); m_Value.Set(readValue, 0); flow.SetValue(FloatValue, readValue); break; case OutputType.Vector2: var vector2 = data.Action.ReadValue(); m_Value = vector2; flow.SetValue(Vector2Value, vector2); break; default: throw new ArgumentOutOfRangeException(); } } } public class OnInputSystemEventButton : OnInputSystemEvent { protected override OutputType OutputType => OutputType.Button; } public class OnInputSystemEventFloat : OnInputSystemEvent { protected override OutputType OutputType => OutputType.Float; } public class OnInputSystemEventVector2 : OnInputSystemEvent { protected override OutputType OutputType => OutputType.Vector2; } } #endif