A game about forced loneliness, made by TACStudios
1using System; 2using System.ComponentModel; 3using System.Reflection; 4using UnityEngine.InputSystem.Utilities; 5using UnityEngine.Scripting; 6 7// [GESTURES] 8// Idea for v2 of the input system: 9// Separate interaction *recognition* from interaction *representation* 10// This will likely also "solve" gestures 11// 12// ATM, an interaction is a prebuilt thing that rolls recognition and representation of an interaction into 13// one single thing. That limits how powerful this can be. There's only ever one interaction coming from each interaction 14// added to a setup. 15// 16// A much more powerful way would be to have the interactions configured on actions and bindings add *recognizers* 17// which then *generate* interactions. This way, a single recognizer could spawn arbitrary many interactions. What the 18// recognizer is attached to (the bindings) would simply act as triggers. Beyond that, the recognizer would have 19// plenty freedom to start, perform, and stop interactions happening in response to input. 20// 21// It'll likely be a breaking change as far as user-implemented interactions go but at least the data as it looks today 22// should work with this just fine. 23 24////TODO: allow interactions to be constrained to a specific InputActionType 25 26////TODO: add way for parameters on interactions and processors to be driven from global value source that is NOT InputSettings 27//// (ATM it's very hard to e.g. have a scale value on gamepad stick bindings which is determined dynamically from player 28//// settings in the game) 29 30////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? 31 32////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 33 34////REVIEW: have a default interaction so that there *always* is an interaction object when processing triggers? 35 36namespace UnityEngine.InputSystem 37{ 38 /// <summary> 39 /// Interface for interaction patterns that drive actions. 40 /// </summary> 41 /// <remarks> 42 /// Actions have a built-in interaction pattern that to some extent depends on their type (<see 43 /// cref="InputActionType"/>, <see cref="InputAction.type"/>). What this means is that when controls 44 /// bound to an action are actuated, the action will initiate an interaction that in turn determines 45 /// when <see cref="InputAction.started"/>, <see cref="InputAction.performed"/>, and <see cref="InputAction.canceled"/> 46 /// are called. 47 /// 48 /// The default interaction (that is, when no interaction has been added to a binding or the 49 /// action that the binding targets) will generally start and perform an action as soon as a control 50 /// is actuated, then perform the action whenever the value of the control changes except if the value 51 /// changes back to the default in which case the action is cancelled. 52 /// 53 /// By writing custom interactions, it is possible to implement different interactions. For example, 54 /// <see cref="Interactions.HoldInteraction"/> will only start when a control is being actuated but 55 /// will only perform the action if the control is held for a minimum amount of time. 56 /// 57 /// Interactions can be stateful and mutate state over time. In fact, interactions will usually 58 /// represent miniature state machines driven directly by input. 59 /// 60 /// Multiple interactions can be applied to the same binding. The interactions will be processed in 61 /// sequence. However, the first interaction that starts the action will get to drive the state of 62 /// the action. If it performs the action, all interactions are reset. If it cancels, the first 63 /// interaction in the list that is in started state will get to take over and drive the action. 64 /// 65 /// This makes it possible to have several interaction patterns on the same action. For example, 66 /// to have a "fire" action that allows for charging, one can have a "Hold" and a "Press" interaction 67 /// in sequence on the action. 68 /// 69 /// <example> 70 /// <code> 71 /// // Create a fire action with two interactions: 72 /// // 1. Hold. Triggers charged firing. Has to come first as otherwise "Press" will immediately perform the action. 73 /// // 2. Press. Triggers instant firing. 74 /// // NOTE: An alternative is to use "Tap;Hold", that is, a "Tap" first and then a "Hold". The difference 75 /// // is relatively minor. In this setup, the "Tap" turns into a "Hold" if the button is held for 76 /// // longer than the tap time whereas in the setup below, the "Hold" turns into a "Press" if the 77 /// // button is released before the hold time has been reached. 78 /// var fireAction = new InputAction(type: InputActionType.Button, interactions: "Hold;Press"); 79 /// fireAction.AddBinding("&lt;Gamepad&gt;/buttonSouth"); 80 /// </code> 81 /// </example> 82 /// 83 /// Custom interactions are automatically registered by reflection but it can also be manually registered using <see cref="InputSystem.RegisterInteraction"/>. This can be 84 /// done at any point during or after startup but has to be done before actions that reference the interaction 85 /// are enabled or have their controls queried. A good point is usually to do it during loading like so: 86 /// 87 /// <example> 88 /// <code> 89 /// #if UNITY_EDITOR 90 /// [InitializeOnLoad] 91 /// #endif 92 /// public class MyInteraction : IInputInteraction 93 /// { 94 /// public void Process(ref InputInteractionContext context) 95 /// { 96 /// // ... 97 /// } 98 /// 99 /// public void Reset() 100 /// { 101 /// } 102 /// 103 /// static MyInteraction() 104 /// { 105 /// InputSystem.RegisterInteraction&lt;MyInteraction&gt;(); 106 /// } 107 /// 108 /// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] 109 /// private static void Initialize() 110 /// { 111 /// // Will execute the static constructor as a side effect. 112 /// } 113 /// } 114 /// </code> 115 /// </example> 116 /// 117 /// If your interaction will only work with a specific type of value (e.g. <c>float</c>), it is better 118 /// to base the implementation on <see cref="IInputInteraction{TValue}"/> instead. While the interface is the 119 /// same, the type parameter communicates to the input system that only controls that have compatible value 120 /// types should be used with your interaction. 121 /// 122 /// Interactions, like processors (<see cref="InputProcessor"/>) and binding composites (<see cref="InputBindingComposite"/>) 123 /// may define their own parameters which can then be configured through the editor UI or set programmatically in 124 /// code. To define a parameter, add a public field to your class that has either a <c>bool</c>, an <c>int</c>, 125 /// a <c>float</c>, or an <c>enum</c> type. To set defaults for the parameters, assign default values 126 /// to the fields. 127 /// 128 /// <example> 129 /// <code> 130 /// public class MyInteraction : IInputInteraction 131 /// { 132 /// public bool boolParameter; 133 /// public int intParameter; 134 /// public float floatParameter; 135 /// public MyEnum enumParameter = MyEnum.C; // Custom default. 136 /// 137 /// public enum MyEnum 138 /// { 139 /// A, 140 /// B, 141 /// C 142 /// } 143 /// 144 /// public void Process(ref InputInteractionContext context) 145 /// { 146 /// // ... 147 /// } 148 /// 149 /// public void Reset() 150 /// { 151 /// } 152 /// } 153 /// 154 /// // The parameters can be configured graphically in the editor or set programmatically in code. 155 /// // NOTE: Enum parameters are represented by their integer values. However, when setting enum parameters 156 /// // graphically in the UI, they will be presented as a dropdown using the available enum values. 157 /// var action = new InputAction(interactions: "MyInteraction(boolParameter=true,intParameter=1,floatParameter=1.2,enumParameter=1); 158 /// </code> 159 /// </example> 160 /// 161 /// A default UI will be presented in the editor UI to configure the parameters of your interaction. 162 /// You can customize this by replacing the default UI with a custom implementation using <see cref="Editor.InputParameterEditor"/>. 163 /// This mechanism is the same as for processors and binding composites. 164 /// 165 /// <example> 166 /// <code> 167 /// #if UNITY_EDITOR 168 /// public class MyCustomInteractionEditor : InputParameterEditor&lt;MyCustomInteraction&gt; 169 /// { 170 /// protected override void OnEnable() 171 /// { 172 /// // Do any setup work you need. 173 /// } 174 /// 175 /// protected override void OnGUI() 176 /// { 177 /// // Use standard Unity UI calls do create your own parameter editor UI. 178 /// } 179 /// } 180 /// #endif 181 /// </code> 182 /// </example> 183 /// </remarks> 184 /// <seealso cref="InputSystem.RegisterInteraction"/> 185 /// <seealso cref="InputBinding.interactions"/> 186 /// <seealso cref="InputAction.interactions"/> 187 /// <seealso cref="Editor.InputParameterEditor"/> 188 /// <seealso cref="InputActionRebindingExtensions.GetParameterValue(InputAction,string,InputBinding)"/> 189 /// <seealso cref="InputActionRebindingExtensions.ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/> 190 public interface IInputInteraction 191 { 192 /// <summary> 193 /// Perform processing of the interaction in response to input. 194 /// </summary> 195 /// <param name="context"></param> 196 /// <remarks> 197 /// This method is called whenever a control referenced in the binding that the interaction sits on 198 /// changes value. The interaction is expected to process the value change and, if applicable, call 199 /// <see cref="InputInteractionContext.Started"/> and/or its related methods to initiate a state change. 200 /// 201 /// Note that if "control disambiguation" (i.e. the process where if multiple controls are bound to 202 /// the same action, the system decides which control gets to drive the action at any one point) is 203 /// in effect -- i.e. when either <see cref="InputActionType.Button"/> or <see cref="InputActionType.Value"/> 204 /// are used but not if <see cref="InputActionType.PassThrough"/> is used -- inputs that the disambiguation 205 /// chooses to ignore will cause this method to not be called. 206 /// 207 /// Note that this method is called on the interaction even when there are multiple interactions 208 /// and the interaction is not the one currently in control of the action (because another interaction 209 /// that comes before it in the list had already started the action). Each interaction will get 210 /// processed independently and the action will decide when to use which interaction to drive the 211 /// action as a whole. 212 /// 213 /// <example> 214 /// <code> 215 /// // Processing for an interaction that will perform the action only if a control 216 /// // is held at least at 3/4 actuation for at least 1 second. 217 /// public void Process(ref InputInteractionContext context) 218 /// { 219 /// var control = context.control; 220 /// 221 /// // See if we're currently tracking a control. 222 /// if (m_Control != null) 223 /// { 224 /// // Ignore any input on a control we're not currently tracking. 225 /// if (m_Control != control) 226 /// return; 227 /// 228 /// // Check if the control is currently actuated past our 3/4 threshold. 229 /// var isStillActuated = context.ControlIsActuated(0.75f); 230 /// 231 /// // See for how long the control has been held. 232 /// var actuationTime = context.time - context.startTime; 233 /// 234 /// if (!isStillActuated) 235 /// { 236 /// // Control is no longer actuated above 3/4 threshold. If it was held 237 /// // for at least a second, perform the action. Otherwise cancel it. 238 /// 239 /// if (actuationTime >= 1) 240 /// context.Performed(); 241 /// else 242 /// context.Cancelled(); 243 /// } 244 /// 245 /// // Control changed value somewhere above 3/4 of its actuation. Doesn't 246 /// // matter to us so no change. 247 /// } 248 /// else 249 /// { 250 /// // We're not already tracking a control. See if the control that just triggered 251 /// // is actuated at least 3/4th of its way. If so, start tracking it. 252 /// 253 /// var isActuated = context.ControlIsActuated(0.75f); 254 /// if (isActuated) 255 /// { 256 /// m_Control = context.control; 257 /// context.Started(); 258 /// } 259 /// } 260 /// } 261 /// 262 /// InputControl m_Control; 263 /// 264 /// public void Reset() 265 /// { 266 /// m_Control = null; 267 /// } 268 /// </code> 269 /// </example> 270 /// </remarks> 271 void Process(ref InputInteractionContext context); 272 273 /// <summary> 274 /// Reset state that the interaction may hold. This should put the interaction back in its original 275 /// state equivalent to no input yet having been received. 276 /// </summary> 277 void Reset(); 278 } 279 280 /// <summary> 281 /// Identical to <see cref="IInputInteraction"/> except that it allows an interaction to explicitly 282 /// advertise the value it expects. 283 /// </summary> 284 /// <typeparam name="TValue">Type of values expected by the interaction</typeparam> 285 /// <remarks> 286 /// Advertising the value type will an interaction type to be filtered out in the UI if the value type 287 /// it has is not compatible with the value type expected by the action. 288 /// 289 /// In all other ways, this interface is identical to <see cref="IInputInteraction"/>. 290 /// </remarks> 291 [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.")] 292 public interface IInputInteraction<TValue> : IInputInteraction 293 where TValue : struct 294 { 295 } 296 297 internal static class InputInteraction 298 { 299 public static TypeTable s_Interactions; 300 301 public static Type GetValueType(Type interactionType) 302 { 303 if (interactionType == null) 304 throw new ArgumentNullException(nameof(interactionType)); 305 306 return TypeHelpers.GetGenericTypeArgumentFromHierarchy(interactionType, typeof(IInputInteraction<>), 0); 307 } 308 309 public static string GetDisplayName(string interaction) 310 { 311 if (string.IsNullOrEmpty(interaction)) 312 throw new ArgumentNullException(nameof(interaction)); 313 314 var interactionType = s_Interactions.LookupTypeRegistration(interaction); 315 if (interactionType == null) 316 return interaction; 317 318 return GetDisplayName(interactionType); 319 } 320 321 public static string GetDisplayName(Type interactionType) 322 { 323 if (interactionType == null) 324 throw new ArgumentNullException(nameof(interactionType)); 325 326 var displayNameAttribute = interactionType.GetCustomAttribute<DisplayNameAttribute>(); 327 if (displayNameAttribute == null) 328 { 329 if (interactionType.Name.EndsWith("Interaction")) 330 return interactionType.Name.Substring(0, interactionType.Name.Length - "Interaction".Length); 331 return interactionType.Name; 332 } 333 334 return displayNameAttribute.DisplayName; 335 } 336 } 337}