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
{
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; }
private InputAction m_Action;
private bool m_WasRunning;
///
/// 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;
m_Action = pi
? pi.actions.FindAction(inputAction.id)
: inputAction.actionMap != null ? inputAction : null;
}
public override void StopListening(GraphStack stack)
{
base.StopListening(stack);
m_Action = null;
}
protected override bool ShouldTrigger(Flow flow, EmptyEventArgs args)
{
if (m_Action == null)
return false;
bool shouldtrigger;
// "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();
}
DoAssignArguments(flow);
// 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;
return shouldtrigger;
}
private void DoAssignArguments(Flow flow)
{
switch (OutputType)
{
case OutputType.Button:
break;
case OutputType.Float:
var readValue = m_Action.ReadValue();
m_Value.Set(readValue, 0);
flow.SetValue(FloatValue, readValue);
break;
case OutputType.Vector2:
var vector2 = m_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