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("<Gamepad>/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<MyInteraction>();
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<MyCustomInteraction>
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}