using System; using System.ComponentModel; using System.Reflection; using UnityEngine.InputSystem.Utilities; using UnityEngine.Scripting; // [GESTURES] // Idea for v2 of the input system: // Separate interaction *recognition* from interaction *representation* // This will likely also "solve" gestures // // ATM, an interaction is a prebuilt thing that rolls recognition and representation of an interaction into // one single thing. That limits how powerful this can be. There's only ever one interaction coming from each interaction // added to a setup. // // A much more powerful way would be to have the interactions configured on actions and bindings add *recognizers* // which then *generate* interactions. This way, a single recognizer could spawn arbitrary many interactions. What the // recognizer is attached to (the bindings) would simply act as triggers. Beyond that, the recognizer would have // plenty freedom to start, perform, and stop interactions happening in response to input. // // It'll likely be a breaking change as far as user-implemented interactions go but at least the data as it looks today // should work with this just fine. ////TODO: allow interactions to be constrained to a specific InputActionType ////TODO: add way for parameters on interactions and processors to be driven from global value source that is NOT InputSettings //// (ATM it's very hard to e.g. have a scale value on gamepad stick bindings which is determined dynamically from player //// settings in the game) ////REVIEW: what about putting an instance of one of these on every resolved control instead of sharing it between all controls resolved from a binding? ////REVIEW: can we have multiple interactions work together on the same binding? E.g. a 'Press' causing a start and a 'Release' interaction causing a performed ////REVIEW: have a default interaction so that there *always* is an interaction object when processing triggers? namespace UnityEngine.InputSystem { /// /// Interface for interaction patterns that drive actions. /// /// /// Actions have a built-in interaction pattern that to some extent depends on their type (, ). What this means is that when controls /// bound to an action are actuated, the action will initiate an interaction that in turn determines /// when , , and /// are called. /// /// The default interaction (i.e. when no interaction has been added to a binding or the /// action that the binding targets) will generally start and perform an action as soon as a control /// is actuated, then perform the action whenever the value of the control changes except if the value /// changes back to the default in which case the action is cancelled. /// /// By writing custom interactions, it is possible to implement different interactions. For example, /// will only start when a control is being actuated but /// will only perform the action if the control is held for a minimum amount of time. /// /// Interactions can be stateful and mutate state over time. In fact, interactions will usually /// represent miniature state machines driven directly by input. /// /// Multiple interactions can be applied to the same binding. The interactions will be processed in /// sequence. However, the first interaction that starts the action will get to drive the state of /// the action. If it performs the action, all interactions are reset. If it cancels, the first /// interaction in the list that is in started state will get to take over and drive the action. /// /// This makes it possible to have several interaction patterns on the same action. For example, /// to have a "fire" action that allows for charging, one can have a "Hold" and a "Press" interaction /// in sequence on the action. /// /// /// /// // Create a fire action with two interactions: /// // 1. Hold. Triggers charged firing. Has to come first as otherwise "Press" will immediately perform the action. /// // 2. Press. Triggers instant firing. /// // NOTE: An alternative is to use "Tap;Hold", i.e. a "Tap" first and then a "Hold". The difference /// // is relatively minor. In this setup, the "Tap" turns into a "Hold" if the button is held for /// // longer than the tap time whereas in the setup below, the "Hold" turns into a "Press" if the /// // button is released before the hold time has been reached. /// var fireAction = new InputAction(type: InputActionType.Button, interactions: "Hold;Press"); /// fireAction.AddBinding("<Gamepad>/buttonSouth"); /// /// /// /// Custom interactions can be registered using . This can be /// done at any point during or after startup but has to be done before actions that reference the interaction /// are enabled or have their controls queried. A good point is usually to do it during loading like so: /// /// /// /// #if UNITY_EDITOR /// [InitializeOnLoad] /// #endif /// public class MyInteraction : IInputInteraction /// { /// public void Process(ref InputInteractionContext context) /// { /// // ... /// } /// /// public void Reset() /// { /// } /// /// static MyInteraction() /// { /// InputSystem.RegisterInteraction<MyInteraction>(); /// } /// /// [RuntimeInitializeOnLoad] /// private static void Initialize() /// { /// // Will execute the static constructor as a side effect. /// } /// } /// /// /// /// If your interaction will only work with a specific type of value (e.g. float), it is better /// to base the implementation on instead. While the interface is the /// same, the type parameter communicates to the input system that only controls that have compatible value /// types should be used with your interaction. /// /// Interactions, like processors () and binding composites () /// may define their own parameters which can then be configured through the editor UI or set programmatically in /// code. To define a parameter, add a public field to your class that has either a bool, an int, /// a float, or an enum type. To set defaults for the parameters, assign default values /// to the fields. /// /// /// /// public class MyInteraction : IInputInteraction /// { /// public bool boolParameter; /// public int intParameter; /// public float floatParameter; /// public MyEnum enumParameter = MyEnum.C; // Custom default. /// /// public enum MyEnum /// { /// A, /// B, /// C /// } /// /// public void Process(ref InputInteractionContext context) /// { /// // ... /// } /// /// public void Reset() /// { /// } /// } /// /// // The parameters can be configured graphically in the editor or set programmatically in code. /// // NOTE: Enum parameters are represented by their integer values. However, when setting enum parameters /// // graphically in the UI, they will be presented as a dropdown using the available enum values. /// var action = new InputAction(interactions: "MyInteraction(boolParameter=true,intParameter=1,floatParameter=1.2,enumParameter=1); /// /// /// /// A default UI will be presented in the editor UI to configure the parameters of your interaction. /// You can customize this by replacing the default UI with a custom implementation using . /// This mechanism is the same as for processors and binding composites. /// /// /// /// #if UNITY_EDITOR /// public class MyCustomInteractionEditor : InputParameterEditor<MyCustomInteraction> /// { /// protected override void OnEnable() /// { /// // Do any setup work you need. /// } /// /// protected override void OnGUI() /// { /// // Use standard Unity UI calls do create your own parameter editor UI. /// } /// } /// #endif /// /// /// /// /// /// /// [Preserve] public interface IInputInteraction { /// /// Perform processing of the interaction in response to input. /// /// /// /// This method is called whenever a control referenced in the binding that the interaction sits on /// changes value. The interaction is expected to process the value change and, if applicable, call /// and/or its related methods to initiate a state change. /// /// Note that if "control disambiguation" (i.e. the process where if multiple controls are bound to /// the same action, the system decides which control gets to drive the action at any one point) is /// in effect -- i.e. when either or /// are used but not if is used -- inputs that the disambiguation /// chooses to ignore will cause this method to not be called. /// /// Note that this method is called on the interaction even when there are multiple interactions /// and the interaction is not the one currently in control of the action (because another interaction /// that comes before it in the list had already started the action). Each interaction will get /// processed independently and the action will decide when to use which interaction to drive the /// action as a whole. /// /// /// /// // Processing for an interaction that will perform the action only if a control /// // is held at least at 3/4 actuation for at least 1 second. /// public void Process(ref InputInteractionContext context) /// { /// var control = context.control; /// /// // See if we're currently tracking a control. /// if (m_Control != null) /// { /// // Ignore any input on a control we're not currently tracking. /// if (m_Control != control) /// return; /// /// // Check if the control is currently actuated past our 3/4 threshold. /// var isStillActuated = context.ControlIsActuated(0.75f); /// /// // See for how long the control has been held. /// var actuationTime = context.time - context.startTime; /// /// if (!isStillActuated) /// { /// // Control is no longer actuated above 3/4 threshold. If it was held /// // for at least a second, perform the action. Otherwise cancel it. /// /// if (actuationTime >= 1) /// context.Performed(); /// else /// context.Cancelled(); /// } /// /// // Control changed value somewhere above 3/4 of its actuation. Doesn't /// // matter to us so no change. /// } /// else /// { /// // We're not already tracking a control. See if the control that just triggered /// // is actuated at least 3/4th of its way. If so, start tracking it. /// /// var isActuated = context.ControlIsActuated(0.75f); /// if (isActuated) /// { /// m_Control = context.control; /// context.Started(); /// } /// } /// } /// /// InputControl m_Control; /// /// public void Reset() /// { /// m_Control = null; /// } /// /// /// void Process(ref InputInteractionContext context); /// /// Reset state that the interaction may hold. This should put the interaction back in its original /// state equivalent to no input yet having been received. /// void Reset(); } /// /// Identical to except that it allows an interaction to explicitly /// advertise the value it expects. /// /// Type of values expected by the interaction /// /// Advertising the value type will an interaction type to be filtered out in the UI if the value type /// it has is not compatible with the value type expected by the action. /// /// In all other ways, this interface is identical to . /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1040:AvoidEmptyInterfaces", Justification = "This interface is used to mark implementing classes to advertise the value it expects. This seems more elegant then the suggestion to use an attribute.")] [Preserve] public interface IInputInteraction : IInputInteraction where TValue : struct { } internal static class InputInteraction { public static TypeTable s_Interactions; public static Type GetValueType(Type interactionType) { if (interactionType == null) throw new ArgumentNullException(nameof(interactionType)); return TypeHelpers.GetGenericTypeArgumentFromHierarchy(interactionType, typeof(IInputInteraction<>), 0); } public static string GetDisplayName(string interaction) { if (string.IsNullOrEmpty(interaction)) throw new ArgumentNullException(nameof(interaction)); var interactionType = s_Interactions.LookupTypeRegistration(interaction); if (interactionType == null) return interaction; return GetDisplayName(interactionType); } public static string GetDisplayName(Type interactionType) { if (interactionType == null) throw new ArgumentNullException(nameof(interactionType)); var displayNameAttribute = interactionType.GetCustomAttribute(); if (displayNameAttribute == null) { if (interactionType.Name.EndsWith("Interaction")) return interactionType.Name.Substring(0, interactionType.Name.Length - "Interaction".Length); return interactionType.Name; } return displayNameAttribute.DisplayName; } } }