A game about forced loneliness, made by TACStudios
at master 59 kB view raw
1using System; 2using System.Collections; 3using System.Collections.Generic; 4using System.Linq.Expressions; 5using System.Reflection; 6using Unity.Collections.LowLevel.Unsafe; 7using UnityEngine.InputSystem.Utilities; 8 9namespace UnityEngine.InputSystem 10{ 11 partial class InputActionRebindingExtensions 12 { 13 /// <summary> 14 /// Return the current value of the given parameter as found on the processors, interactions, or composites 15 /// of the action's current bindings. 16 /// </summary> 17 /// <param name="action">Action on whose bindings to look for the value of the given parameter.</param> 18 /// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the 19 /// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the 20 /// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c> 21 /// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param> 22 /// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only 23 /// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will be taken into account.</param> 24 /// <returns>The current value of the given parameter or <c>null</c> if the parameter could not be found.</returns> 25 /// <remarks> 26 /// Parameters are found on interactions (<see cref="IInputInteraction"/>), processors (<see cref="InputProcessor"/>), and 27 /// composites (see <see cref="InputBindingComposite"/>) that are applied to bindings. For example, the following binding 28 /// adds a <c>Hold</c> interaction with a custom <c>duration</c> parameter on top of binding to the gamepad's A button: 29 /// 30 /// <example> 31 /// <code> 32 /// new InputBinding 33 /// { 34 /// path = "&lt;Gamepad&gt;/buttonSouth", 35 /// interactions = "hold(duration=0.6)" 36 /// }; 37 /// </code> 38 /// </example> 39 /// 40 /// In the editor UI, parameters are set graphically from the properties sections in the right-most pane 41 /// in the action editor when an action or a binding is selected. 42 /// 43 /// When the binding above is applied to an action, the <c>duration</c> parameter from the <c>Hold</c> interaction can be 44 /// queried like so: 45 /// 46 /// <example> 47 /// <code> 48 /// action.GetParameterValue("duration") // Returns 0.6 49 /// </code> 50 /// </example> 51 /// 52 /// Note that if there are multiple objects on the action that use the same parameter name, the value of the <em>first</em> parameter 53 /// that is encountered is returned. Also note that this method will create GC heap garbage. 54 /// 55 /// The type of object to query the parameter from can be include in the <paramref name="name"/> parameter. For example, if 56 /// an action has both a <see cref="Interactions.TapInteraction"/> and a <see cref="Interactions.HoldInteraction"/> on it, the 57 /// <c>duration</c> parameter can be queried independently like so: 58 /// 59 /// <example> 60 /// <code> 61 /// // Query "duration" from "hold": 62 /// action.GetParameterValue("hold:duration"); 63 /// 64 /// // Query "duration" from "tap": 65 /// action.GetParameterValue("tap:duration"); 66 /// </code> 67 /// </example> 68 /// 69 /// The names used here to identify the object holding the parameter are the same used by <see cref="InputSystem.RegisterInteraction"/>, 70 /// <see cref="InputSystem.RegisterBindingComposite"/>, and <see cref="InputSystem.RegisterProcessor"/>. 71 /// </remarks> 72 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c></exception> 73 /// <seealso cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/> 74 /// <seealso cref="ApplyBindingOverride(InputAction,string,string,string)"/> 75 /// <seealso cref="Editor.InputParameterEditor"/> 76 public static PrimitiveValue? GetParameterValue(this InputAction action, string name, InputBinding bindingMask = default) 77 { 78 if (action == null) 79 throw new ArgumentNullException(nameof(action)); 80 if (string.IsNullOrEmpty(name)) 81 throw new ArgumentNullException(nameof(name)); 82 83 return action.GetParameterValue(new ParameterOverride(name, bindingMask)); 84 } 85 86 private static PrimitiveValue? GetParameterValue(this InputAction action, ParameterOverride parameterOverride) 87 { 88 parameterOverride.bindingMask.action = action.name; 89 90 var actionMap = action.GetOrCreateActionMap(); 91 actionMap.ResolveBindingsIfNecessary(); 92 foreach (var parameter in new ParameterEnumerable(actionMap.m_State, parameterOverride, actionMap.m_MapIndexInState)) 93 { 94 var value = parameter.field.GetValue(parameter.instance); 95 return PrimitiveValue.FromObject(value); 96 } 97 98 return null; 99 } 100 101 /// <summary> 102 /// Return the current value of the given parameter as found on the processors, interactions, or composites 103 /// of the action's current bindings. 104 /// </summary> 105 /// <param name="action">Action on whose bindings to look for the value of the given parameter.</param> 106 /// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the 107 /// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the 108 /// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c> 109 /// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param> 110 /// <param name="bindingIndex">Index of the binding in <paramref name="action"/>'s <see cref="InputAction.bindings"/> 111 /// to look for processors, interactions, and composites on.</param> 112 /// <returns>The current value of the given parameter or <c>null</c> if the parameter not could be found.</returns> 113 /// <remarks> 114 /// This method is a variation of <see cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/> 115 /// to specifically target a single binding by index. Otherwise, the method is identical in functionality. 116 /// </remarks> 117 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c></exception> 118 public static PrimitiveValue? GetParameterValue(this InputAction action, string name, int bindingIndex) 119 { 120 if (action == null) 121 throw new ArgumentNullException(nameof(action)); 122 if (string.IsNullOrEmpty(name)) 123 throw new ArgumentNullException(nameof(name)); 124 if (bindingIndex < 0) 125 throw new ArgumentOutOfRangeException(nameof(bindingIndex)); 126 127 var indexOnMap = action.BindingIndexOnActionToBindingIndexOnMap(bindingIndex); 128 var bindingMask = new InputBinding { id = action.GetOrCreateActionMap().bindings[indexOnMap].id }; 129 130 return action.GetParameterValue(name, bindingMask); 131 } 132 133 /// <summary> 134 /// Return the current value of the given parameter as found on the processors, interactions, or composites 135 /// of the action's current bindings. 136 /// </summary> 137 /// <param name="action">Action on whose bindings to look for the value of the given parameter.</param> 138 /// <param name="expr">An expression such as <c>(TapInteraction x) => x.duration</c> that determines the 139 /// name and type of the parameter being looked for.</param> 140 /// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only 141 /// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will be taken into account.</param> 142 /// <returns>The current value of the given parameter or <c>null</c> if the parameter not could be found.</returns> 143 /// <remarks> 144 /// This method is a variation of <see cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/> 145 /// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is 146 /// type-safe and does not involve strings. 147 /// 148 /// <example> 149 /// <code> 150 /// // Get the "duration" parameter from a TapInteraction. 151 /// // This is equivalent to calling GetParameterValue("tap:duration") 152 /// // but will return a float? instead of a PrimitiveValue?. 153 /// action.GetParameterValue((TapInteraction x) => x.duration) 154 /// </code> 155 /// </example> 156 /// </remarks> 157 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="expr"/> is <c>null</c></exception> 158 /// <seealso cref="ApplyParameterOverride{TObject,TValue}(InputAction,Expression{Func{TObject,TValue}},TValue,InputBinding)"/> 159 public static unsafe TValue? GetParameterValue<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expr, InputBinding bindingMask = default) 160 where TValue : struct 161 { 162 if (action == null) 163 throw new ArgumentNullException(nameof(action)); 164 if (expr == null) 165 throw new ArgumentNullException(nameof(expr)); 166 167 var parameterOverride = ExtractParameterOverride(expr, bindingMask); 168 var value = action.GetParameterValue(parameterOverride); 169 170 if (value == null) 171 return null; 172 173 // Type is guaranteed to match but just in case, 174 // make extra sure with a check here. 175 if (Type.GetTypeCode(typeof(TValue)) == value.Value.type) 176 { 177 // Can't just cast here so use UnsafeUtility to work around that. 178 var v = value.Value; 179 var result = default(TValue); 180 UnsafeUtility.MemCpy(UnsafeUtility.AddressOf(ref result), 181 v.valuePtr, 182 UnsafeUtility.SizeOf<TValue>()); 183 return result; 184 } 185 186 // Shouldn't get here but just in case, do a conversion using C#'s Convert 187 // machinery as a fallback. 188 return (TValue)Convert.ChangeType(value.Value.ToObject(), typeof(TValue)); 189 } 190 191 /// <summary> 192 /// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>, 193 /// and <see cref="InputProcessor"/> objects found on the <see cref="InputAction.bindings"/> of <paramref name="action"/>. 194 /// </summary> 195 /// <param name="action">An action on whose <see cref="InputAction.bindings"/> to look for objects to set 196 /// the parameter value on.</param> 197 /// <param name="expr">An expression such as <c>(TapInteraction x) => x.duration</c> that determines the 198 /// name and type of the parameter whose value to set.</param> 199 /// <param name="value">New value to assign to the parameter.</param> 200 /// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only 201 /// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will have the override applied to them.</param> 202 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="expr"/> is <c>null</c> 203 /// or empty.</exception> 204 /// <remarks> 205 /// This method is a variation of <see cref="ApplyParameterOverride(InputAction,string,PrimitiveValue,InputBinding)"/> 206 /// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is 207 /// type-safe and does not involve strings. 208 /// 209 /// <example> 210 /// <code> 211 /// // Override the "duration" parameter from a TapInteraction. 212 /// // This is equivalent to calling ApplyParameterOverride("tap:duration", 0.4f). 213 /// action.ApplyParameterOverride((TapInteraction x) => x.duration, 0.4f); 214 /// </code> 215 /// </example> 216 /// </remarks> 217 /// <seealso cref="GetParameterValue{TObject,TValue}(InputAction,Expression{Func{TObject,TValue}},InputBinding)"/> 218 public static void ApplyParameterOverride<TObject, TValue>(this InputAction action, Expression<Func<TObject, TValue>> expr, TValue value, 219 InputBinding bindingMask = default) 220 where TValue : struct 221 { 222 if (action == null) 223 throw new ArgumentNullException(nameof(action)); 224 if (expr == null) 225 throw new ArgumentNullException(nameof(expr)); 226 227 var actionMap = action.GetOrCreateActionMap(); 228 actionMap.ResolveBindingsIfNecessary(); 229 bindingMask.action = action.name; 230 231 var parameterOverride = ExtractParameterOverride(expr, bindingMask, PrimitiveValue.From(value)); 232 233 ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState, 234 ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount, 235 parameterOverride); 236 } 237 238 /// <summary> 239 /// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>, 240 /// and <see cref="InputProcessor"/> objects found on the <see cref="InputActionMap.bindings"/> of <paramref name="actionMap"/>. 241 /// </summary> 242 /// <param name="actionMap">An action on whose <see cref="InputActionMap.bindings"/> to look for objects to set 243 /// the parameter value on.</param> 244 /// <param name="expr">An expression such as <c>(TapInteraction x) => x.duration</c> that determines the 245 /// name and type of the parameter whose value to set.</param> 246 /// <param name="value">New value to assign to the parameter.</param> 247 /// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only 248 /// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will have the override applied to them.</param> 249 /// <exception cref="ArgumentNullException"><paramref name="actionMap"/> is <c>null</c> -or- <paramref name="expr"/> is <c>null</c> 250 /// or empty.</exception> 251 /// <remarks> 252 /// This method is a variation of <see cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/> 253 /// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is 254 /// type-safe and does not involve strings. 255 /// 256 /// <example> 257 /// <code> 258 /// // Override the "duration" parameter from a TapInteraction. 259 /// // This is equivalent to calling mApplyParameterOverride("tap:duration", 0.4f). 260 /// actionMap.ApplyParameterOverride((TapInteraction x) => x.duration, 0.4f); 261 /// </code> 262 /// </example> 263 /// </remarks> 264 /// <seealso cref="GetParameterValue{TObject,TValue}(InputAction,Expression{Func{TObject,TValue}},InputBinding)"/> 265 public static void ApplyParameterOverride<TObject, TValue>(this InputActionMap actionMap, Expression<Func<TObject, TValue>> expr, TValue value, 266 InputBinding bindingMask = default) 267 where TValue : struct 268 { 269 if (actionMap == null) 270 throw new ArgumentNullException(nameof(actionMap)); 271 if (expr == null) 272 throw new ArgumentNullException(nameof(expr)); 273 274 actionMap.ResolveBindingsIfNecessary(); 275 276 var parameterOverride = ExtractParameterOverride(expr, bindingMask, PrimitiveValue.From(value)); 277 278 ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState, 279 ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount, 280 parameterOverride); 281 } 282 283 /// <summary> 284 /// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>, 285 /// and <see cref="InputProcessor"/> objects found on the <see cref="InputActionMap.bindings"/> of the <see cref="InputActionAsset.actionMaps"/> 286 /// in <paramref name="asset"/>. 287 /// </summary> 288 /// <param name="asset">An asset on whose <see cref="InputActionMap.bindings"/> to look for objects to set 289 /// the parameter value on.</param> 290 /// <param name="expr">An expression such as <c>(TapInteraction x) => x.duration</c> that determines the 291 /// name and type of the parameter whose value to set.</param> 292 /// <param name="value">New value to assign to the parameter.</param> 293 /// <param name="bindingMask">Optional mask that determines on which bindings to look for objects with parameters. If used, only 294 /// bindings that match (see <see cref="InputBinding.Matches"/>) the given mask will have the override applied to them.</param> 295 /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or- <paramref name="expr"/> is <c>null</c> 296 /// or empty.</exception> 297 /// <remarks> 298 /// This method is a variation of <see cref="ApplyParameterOverride(InputActionAsset,string,PrimitiveValue,InputBinding)"/> 299 /// that encapsulates a reference to the name of the parameter and the type of object it is found on in a way that is 300 /// type-safe and does not involve strings. 301 /// 302 /// <example> 303 /// <code> 304 /// // Override the "duration" parameter from a TapInteraction. 305 /// // This is equivalent to calling mApplyParameterOverride("tap:duration", 0.4f). 306 /// asset.ApplyParameterOverride((TapInteraction x) => x.duration, 0.4f); 307 /// </code> 308 /// </example> 309 /// </remarks> 310 /// <seealso cref="GetParameterValue{TObject,TValue}(InputAction,Expression{Func{TObject,TValue}},InputBinding)"/> 311 public static void ApplyParameterOverride<TObject, TValue>(this InputActionAsset asset, Expression<Func<TObject, TValue>> expr, TValue value, 312 InputBinding bindingMask = default) 313 where TValue : struct 314 { 315 if (asset == null) 316 throw new ArgumentNullException(nameof(asset)); 317 if (expr == null) 318 throw new ArgumentNullException(nameof(expr)); 319 320 asset.ResolveBindingsIfNecessary(); 321 322 var parameterOverride = ExtractParameterOverride(expr, bindingMask, PrimitiveValue.From(value)); 323 324 ApplyParameterOverride(asset.m_SharedStateForAllMaps, -1, 325 ref asset.m_ParameterOverrides, ref asset.m_ParameterOverridesCount, 326 parameterOverride); 327 } 328 329 private static ParameterOverride ExtractParameterOverride<TObject, TValue>(Expression<Func<TObject, TValue>> expr, 330 InputBinding bindingMask = default, PrimitiveValue value = default) 331 { 332 if (!(expr is LambdaExpression lambda)) 333 throw new ArgumentException($"Expression must be a LambdaExpression but was a {expr.GetType().Name} instead", nameof(expr)); 334 335 if (!(lambda.Body is MemberExpression body)) 336 { 337 // If the field type in the lambda doesn't match the TValue type being used, 338 // but there is a coercion, the compiler will automatically insert a Convert(x.name, TValue) 339 // expression. 340 if (lambda.Body is UnaryExpression unary && unary.NodeType == ExpressionType.Convert && unary.Operand is MemberExpression b) 341 { 342 body = b; 343 } 344 else 345 { 346 throw new ArgumentException( 347 $"Body in LambdaExpression must be a MemberExpression (x.name) but was a {expr.GetType().Name} instead", 348 nameof(expr)); 349 } 350 } 351 352 string objectRegistrationName; 353 if (typeof(InputProcessor).IsAssignableFrom(typeof(TObject))) 354 objectRegistrationName = InputProcessor.s_Processors.FindNameForType(typeof(TObject)); 355 else if (typeof(IInputInteraction).IsAssignableFrom(typeof(TObject))) 356 objectRegistrationName = InputInteraction.s_Interactions.FindNameForType(typeof(TObject)); 357 else if (typeof(InputBindingComposite).IsAssignableFrom(typeof(TObject))) 358 objectRegistrationName = InputBindingComposite.s_Composites.FindNameForType(typeof(TObject)); 359 else 360 throw new ArgumentException( 361 $"Given type must be an InputProcessor, IInputInteraction, or InputBindingComposite (was {typeof(TObject).Name})", 362 nameof(TObject)); 363 364 return new ParameterOverride(objectRegistrationName, body.Member.Name, bindingMask, value); 365 } 366 367 /// <summary> 368 /// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>, 369 /// and <see cref="InputProcessor"/> objects found on the <see cref="InputActionMap.bindings"/> of <paramref name="actionMap"/>. 370 /// </summary> 371 /// <param name="actionMap">An action map on whose <see cref="InputActionMap.bindings"/> to look for objects to set 372 /// the parameter value on.</param> 373 /// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the 374 /// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the 375 /// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c> 376 /// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param> 377 /// <param name="value">New value to assign to the parameter.</param> 378 /// <param name="bindingMask">A binding mask that determines which of <paramref name="actionMap"/>'s <see cref="InputActionMap.bindings"/> 379 /// to apply the override to. By default this is empty which leads to the override to be applied to all bindings in the map.</param> 380 /// <remarks> 381 /// This method both directly applies the new value and also stores the override internally. 382 /// 383 /// If an override for the same parameter <paramref name="name"/> and with the same <paramref name="bindingMask"/> already exists, 384 /// its value is simply updated. No new override will be created. 385 /// 386 /// You can use this method to set parameters (public fields) on composites, interactions, and processors that are created 387 /// from bindings. 388 /// 389 /// <example> 390 /// <code> 391 /// // Create an action map with two actions. 392 /// var map = new InputActionMap(); 393 /// var action1 = map.AddAction("action1"); 394 /// var action2 = map.AddAction("action2"); 395 /// 396 /// // Add a binding to each action to which a "ClampProcessor" is applied. 397 /// // This processor has two parameters: 398 /// // - "min" (float) 399 /// // - "max" (float) 400 /// action1.AddBinding("&gt;Gamepad&gt;/rightTrigger", processors: "clamp(min=0.2,max=0.8)"); 401 /// action2.AddBinding("&gt;Gamepad&gt;/leftTrigger", processors: "clamp(min=0.2,max=0.8)"); 402 /// 403 /// // Apply parameter overrides to set the values differently. 404 /// // This will apply the setting to *both* the bindings on action1 *and* action2. 405 /// map.ApplyParameterOverride("min", 0.3f); 406 /// map.ApplyParameterOverride("max", 0.9f); 407 /// </code> 408 /// </example> 409 /// 410 /// An override can optionally be directed at a specific type of object. 411 /// 412 /// <example> 413 /// <code> 414 /// map.ApplyParameterOverride("clamp:min", 0.3f); 415 /// map.ApplyParameterOverride("clamp:max", 0.9f); 416 /// </code> 417 /// </example> 418 /// 419 /// By default, the parameter override will apply to all bindings in the map. To limit the override 420 /// to specific bindings, you can supply a <paramref name="bindingMask"/>. 421 /// 422 /// <example> 423 /// <code> 424 /// // Apply a parameter override only to action1. 425 /// map.ApplyBindingOverride("clamp:min", 0.25f, new InputBinding { action = action1.name }); 426 /// 427 /// // Apply a parameter override only to a specific binding path. 428 /// map.ApplyBindingOverride("clamp:min", 0.4f, new InputBinding { path = "&lt;Gamepad&gt;/leftTrigger" }); 429 /// </code> 430 /// </example> 431 /// 432 /// If multiple overrides exist for the same parameter, an attempt is made to choose the override that is most specific. 433 /// Say, that you apply an override for <c>"duration"</c> on an entire <see cref="InputActionAsset"/> using 434 /// <see cref="ApplyParameterOverride(InputActionAsset,String,PrimitiveValue,InputBinding)"/>. But then you also apply 435 /// an override to just an individual <see cref="InputAction"/> inside the asset. In this case, the <c>"duration"</c> 436 /// override for just that action will be applied to bindings of that action and the override inside the asset will 437 /// be applied to bindings of all other actions. Note that if multiple overrides exist that could all be considered 438 /// equally valid, the behavior is undecided. 439 /// 440 /// Note that parameter overrides stay in place on the map. Like binding overrides, however, they are not 441 /// automatically persisted and thus need to be reapplied when actions are loaded from assets. This will, however, be applied 442 /// automatically to bindings added to the action in the future as well as whenever bindings are resolved to controls. 443 /// </remarks> 444 /// <exception cref="ArgumentNullException"><paramref name="actionMap"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c> 445 /// or empty.</exception> 446 /// <seealso cref="GetParameterValue(InputAction,string,InputBinding)"/> 447 public static void ApplyParameterOverride(this InputActionMap actionMap, string name, PrimitiveValue value, InputBinding bindingMask = default) 448 { 449 if (actionMap == null) 450 throw new ArgumentNullException(nameof(actionMap)); 451 if (string.IsNullOrEmpty(name)) 452 throw new ArgumentNullException(nameof(name)); 453 454 actionMap.ResolveBindingsIfNecessary(); 455 456 ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState, 457 ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount, 458 new ParameterOverride(name, bindingMask, value)); 459 } 460 461 /// <summary> 462 /// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>, 463 /// and <see cref="InputProcessor"/> objects found on the <see cref="InputActionMap.bindings"/> of each of the <see cref="InputActionAsset.actionMaps"/> 464 /// in <paramref name="asset"/>. 465 /// </summary> 466 /// <param name="asset">An <c>.inputactions</c> asset on whose <see cref="InputActionMap.bindings"/> to look for objects to set 467 /// the parameter value on.</param> 468 /// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the 469 /// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the 470 /// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c> 471 /// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param> 472 /// <param name="value">New value to assign to the parameter.</param> 473 /// <param name="bindingMask">A binding mask that determines which of the <see cref="InputActionMap.bindings"/> 474 /// to apply the override to. By default this is empty which leads to the override to be applied to all bindings in the asset.</param> 475 /// <remarks> 476 /// This method both directly applies the new value and also stores the override internally. 477 /// 478 /// If an override for the same parameter <paramref name="name"/> and with the same <paramref name="bindingMask"/> already exists, 479 /// its value is simply updated. No new override will be created. 480 /// 481 /// You can use this method to set parameters (public fields) on composites, interactions, and processors that are created 482 /// from bindings. 483 /// 484 /// <example> 485 /// <code> 486 /// // Create an asset with one action map and two actions. 487 /// var asset = ScriptableObject.CreateInstance&lt;InputActionAsset&gt;(); 488 /// var map = asset.AddActionMap("map"); 489 /// var action1 = map.AddAction("action1"); 490 /// var action2 = map.AddAction("action2"); 491 /// 492 /// // Add a binding to each action to which a "ClampProcessor" is applied. 493 /// // This processor has two parameters: 494 /// // - "min" (float) 495 /// // - "max" (float) 496 /// action1.AddBinding("&gt;Gamepad&gt;/rightTrigger", processors: "clamp(min=0.2,max=0.8)"); 497 /// action2.AddBinding("&gt;Gamepad&gt;/leftTrigger", processors: "clamp(min=0.2,max=0.8)"); 498 /// 499 /// // Apply parameter overrides to set the values differently. 500 /// // This will apply the setting to *both* the bindings on action1 *and* action2. 501 /// asset.ApplyParameterOverride("min", 0.3f); 502 /// asset.ApplyParameterOverride("max", 0.9f); 503 /// </code> 504 /// </example> 505 /// 506 /// An override can optionally be directed at a specific type of object. 507 /// 508 /// <example> 509 /// <code> 510 /// asset.ApplyParameterOverride("clamp:min", 0.3f); 511 /// asset.ApplyParameterOverride("clamp:max", 0.9f); 512 /// </code> 513 /// </example> 514 /// 515 /// By default, the parameter override will apply to all bindings in the asset. To limit the override 516 /// to specific bindings, you can supply a <paramref name="bindingMask"/>. 517 /// 518 /// <example> 519 /// <code> 520 /// // Apply a parameter override only to action1. 521 /// asset.ApplyBindingOverride("clamp:min", 0.25f, new InputBinding { action = action1.name }); 522 /// 523 /// // Apply a parameter override only to a specific binding path. 524 /// asset.ApplyBindingOverride("clamp:min", 0.4f, new InputBinding { path = "&lt;Gamepad&gt;/leftTrigger" }); 525 /// </code> 526 /// </example> 527 /// 528 /// If multiple overrides exist for the same parameter, an attempt is made to choose the override that is most specific. 529 /// Say, that you apply an override for <c>"duration"</c> on an entire <see cref="InputActionAsset"/> using 530 /// <see cref="ApplyParameterOverride(InputActionAsset,String,PrimitiveValue,InputBinding)"/>. But then you also apply 531 /// an override to just an individual <see cref="InputAction"/> inside the asset. In this case, the <c>"duration"</c> 532 /// override for just that action will be applied to bindings of that action and the override inside the asset will 533 /// be applied to bindings of all other actions. Note that if multiple overrides exist that could all be considered 534 /// equally valid, the behavior is undecided. 535 /// 536 /// Note that parameter overrides stay in place on the map. Like binding overrides, however, they are not 537 /// automatically persisted and thus need to be reapplied when actions are loaded from assets. This will, however, be applied 538 /// automatically to bindings added to the action in the future as well as whenever bindings are resolved to controls. 539 /// </remarks> 540 /// <exception cref="ArgumentNullException"><paramref name="asset"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c> 541 /// or empty.</exception> 542 /// <seealso cref="GetParameterValue(InputAction,string,InputBinding)"/> 543 public static void ApplyParameterOverride(this InputActionAsset asset, string name, PrimitiveValue value, InputBinding bindingMask = default) 544 { 545 if (asset == null) 546 throw new ArgumentNullException(nameof(asset)); 547 if (string.IsNullOrEmpty(name)) 548 throw new ArgumentNullException(nameof(name)); 549 550 asset.ResolveBindingsIfNecessary(); 551 552 ApplyParameterOverride(asset.m_SharedStateForAllMaps, -1, 553 ref asset.m_ParameterOverrides, ref asset.m_ParameterOverridesCount, 554 new ParameterOverride(name, bindingMask, value)); 555 } 556 557 /// <summary> 558 /// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>, 559 /// and <see cref="InputProcessor"/> objects found on the <see cref="InputAction.bindings"/> of <paramref name="action"/>. 560 /// </summary> 561 /// <param name="action">An action on whose <see cref="InputAction.bindings"/> to look for objects to set 562 /// the parameter value on.</param> 563 /// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the 564 /// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the 565 /// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c> 566 /// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param> 567 /// <param name="value">New value to assign to the parameter.</param> 568 /// <param name="bindingMask">A binding mask that determines which of <paramref name="action"/>'s <see cref="InputAction.bindings"/> 569 /// to apply the override to. By default this is empty which leads to the override to be applied to all bindings of the action.</param> 570 /// <remarks> 571 /// This method both directly applies the new value and also stores the override internally. 572 /// 573 /// If an override for the same parameter <paramref name="name"/> on the same <paramref name="action"/> and with the same 574 /// <paramref name="bindingMask"/> already exists, its value is simply updated. No new override will be created. 575 /// 576 /// You can use this method to set parameters (public fields) on composites, interactions, and processors that are created 577 /// from bindings. 578 /// 579 /// <example> 580 /// <code> 581 /// // Create an action with a binding that has a "ClampProcessor" applied to it. 582 /// // This processor has two parameters: 583 /// // - "min" (float) 584 /// // - "max" (float) 585 /// var action = new InputAction(binding: "&gt;Gamepad&gt;/rightTrigger", processors: "clamp(min=0.2,max=0.8)"); 586 /// 587 /// // Apply parameter overrides to set the values differently. 588 /// action.ApplyParameterOverride("min", 0.3f); 589 /// action.ApplyParameterOverride("max", 0.9f); 590 /// </code> 591 /// </example> 592 /// 593 /// An override can optionally be directed at a specific type of object. 594 /// 595 /// <example> 596 /// <code> 597 /// // Create an action with both a "tap" and a "hold" interaction. Both have a 598 /// // "duration" parameter. 599 /// var action = new InputAction(binding: "&lt;Gamepad&gt;/buttonSouth", interactions: "tap;hold"); 600 /// 601 /// // Apply parameter overrides individually to the two. 602 /// action.ApplyParameterOverride("tap:duration", 0.6f); 603 /// action.ApplyParameterOverride("hold:duration", 4f); 604 /// </code> 605 /// </example> 606 /// 607 /// By default, the parameter override will apply to all bindings on the action. To limit the override 608 /// to specific bindings, you can supply a <paramref name="bindingMask"/>. 609 /// 610 /// <example> 611 /// <code> 612 /// // Create a "look" style action with a mouse and a gamepad binding. 613 /// var lookAction = new InputAction(); 614 /// lookAction.AddBinding("&lt;Mouse&gt;/delta", processors: "scaleVector2", groups: "Mouse"); 615 /// lookAction.AddBinding("&lt;Gamepad&gt;/rightStick", processors: "scaleVector2", groups: "Gamepad"); 616 /// 617 /// // Override scaling of the mouse delta individually. 618 /// lookAction.ApplyBindingOverride("scaleVector2:x", 0.25f, InputBinding.MaskByGroup("Mouse")); 619 /// lookAction.ApplyBindingOverride("scaleVector2:y", 0.25f, InputBinding.MaskByGroup("Mouse")); 620 /// 621 /// // Can also do that by path. 622 /// lookAction.ApplyBindingOverride("scaleVector2:x", 0.25f, new InputBinding("&lt;Mouse&gt;/delta")); 623 /// lookAction.ApplyBindingOverride("scaleVector2:y", 0.25f, new InputBinding("&lt;Mouse&gt;/delta")); 624 /// </code> 625 /// </example> 626 /// 627 /// Note that parameter overrides stay in place on the action. Like binding overrides, however, they are not 628 /// automatically persisted and thus need to be reapplied when actions are loaded from assets. This will, however, be applied 629 /// automatically to bindings added to the action in the future as well as whenever bindings for the action are resolved. 630 /// </remarks> 631 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c> 632 /// or empty.</exception> 633 /// <seealso cref="GetParameterValue(InputAction,string,InputBinding)"/> 634 public static void ApplyParameterOverride(this InputAction action, string name, PrimitiveValue value, InputBinding bindingMask = default) 635 { 636 if (action == null) 637 throw new ArgumentNullException(nameof(action)); 638 if (name == null) 639 throw new ArgumentNullException(nameof(name)); 640 641 var actionMap = action.GetOrCreateActionMap(); 642 actionMap.ResolveBindingsIfNecessary(); 643 bindingMask.action = action.name; 644 645 ApplyParameterOverride(actionMap.m_State, actionMap.m_MapIndexInState, 646 ref actionMap.m_ParameterOverrides, ref actionMap.m_ParameterOverridesCount, 647 new ParameterOverride(name, bindingMask, value)); 648 } 649 650 /// <summary> 651 /// Set the value of the given parameter on the <see cref="InputBindingComposite"/>, <see cref="IInputInteraction"/>, 652 /// and <see cref="InputProcessor"/> objects found on the <see cref="InputAction.bindings"/> of <paramref name="action"/>. 653 /// </summary> 654 /// <param name="action">An action on whose <see cref="InputAction.bindings"/> to look for objects to set 655 /// the parameter value on.</param> 656 /// <param name="name">Name of the parameter to get the value of. Case-insensitive. This can either be just the name of the 657 /// parameter (like <c>"duration"</c> or expressed as <c>nameof(TapInteraction.duration)</c>) or can be prefixed with the 658 /// type of object to get the parameter value from. For example, <c>"tap:duration"</c> will specifically get the <c>"duration"</c> 659 /// parameter from the object registered as <c>"tap"</c> (which will usually be <see cref="Interactions.TapInteraction"/>).</param> 660 /// <param name="value">New value to assign to the parameter.</param> 661 /// <param name="bindingIndex">Index of the binding in <see cref="InputAction.bindings"/> of <paramref name="action"/> to which 662 /// to restrict the parameter override to.</param> 663 /// <exception cref="ArgumentOutOfRangeException"><paramref name="bindingIndex"/> is negative or equal or greater than the number of 664 /// <see cref="InputAction.bindings"/> of <paramref name="action"/>.</exception> 665 /// <exception cref="ArgumentNullException"><paramref name="action"/> is <c>null</c> -or- <paramref name="name"/> is <c>null</c> 666 /// or empty.</exception> 667 /// <remarks> 668 /// This method is a variation of <see cref="ApplyParameterOverride(InputActionMap,string,PrimitiveValue,InputBinding)"/> which 669 /// allows specifying a binding by index. It otherwise behaves identically to that method. 670 /// </remarks> 671 public static void ApplyParameterOverride(this InputAction action, string name, PrimitiveValue value, int bindingIndex) 672 { 673 if (action == null) 674 throw new ArgumentNullException(nameof(action)); 675 if (string.IsNullOrEmpty(name)) 676 throw new ArgumentNullException(nameof(name)); 677 if (bindingIndex < 0) 678 throw new ArgumentOutOfRangeException(nameof(bindingIndex)); 679 680 var indexOnMap = action.BindingIndexOnActionToBindingIndexOnMap(bindingIndex); 681 var bindingMask = new InputBinding { id = action.GetOrCreateActionMap().bindings[indexOnMap].id }; 682 683 action.ApplyParameterOverride(name, value, bindingMask); 684 } 685 686 private static void ApplyParameterOverride(InputActionState state, int mapIndex, 687 ref ParameterOverride[] parameterOverrides, ref int parameterOverridesCount, ParameterOverride parameterOverride) 688 { 689 // Update the parameter overrides on the map or asset. 690 var haveExistingOverride = false; 691 if (parameterOverrides != null) 692 { 693 // Try to find existing override. 694 for (var i = 0; i < parameterOverridesCount; ++i) 695 { 696 ref var p = ref parameterOverrides[i]; 697 if (string.Equals(p.objectRegistrationName, parameterOverride.objectRegistrationName, StringComparison.OrdinalIgnoreCase) && 698 string.Equals(p.parameter, parameterOverride.parameter, StringComparison.OrdinalIgnoreCase) && 699 p.bindingMask == parameterOverride.bindingMask) 700 { 701 haveExistingOverride = true; 702 // Update value on existing override. 703 p = parameterOverride; 704 break; 705 } 706 } 707 } 708 if (!haveExistingOverride) 709 { 710 // Add new override. 711 ArrayHelpers.AppendWithCapacity(ref parameterOverrides, ref parameterOverridesCount, parameterOverride); 712 } 713 714 // Set value on all current processor and/or interaction instances that use the parameter. 715 foreach (var parameter in new ParameterEnumerable(state, parameterOverride, mapIndex)) 716 { 717 // We cannot just blindly apply the parameter here as the override we have set may be less 718 // specific than an override we already have applied. So instead, we look up the most specific 719 // override and set that. 720 var actionMap = state.GetActionMap(parameter.bindingIndex); 721 ref var binding = ref state.GetBinding(parameter.bindingIndex); 722 var overrideToApply = ParameterOverride.Find(actionMap, ref binding, parameterOverride.parameter, 723 parameterOverride.objectRegistrationName); 724 if (overrideToApply.HasValue) 725 { 726 var fieldTypeCode = Type.GetTypeCode(parameter.field.FieldType); 727 parameter.field.SetValue(parameter.instance, overrideToApply.Value.value.ConvertTo(fieldTypeCode).ToObject()); 728 } 729 } 730 } 731 732 internal struct Parameter 733 { 734 public object instance; 735 public FieldInfo field; 736 public int bindingIndex; 737 } 738 739 // Finds all instances of a parameter in one or more actions. 740 private struct ParameterEnumerable : IEnumerable<Parameter> 741 { 742 private InputActionState m_State; 743 private ParameterOverride m_Parameter; 744 private int m_MapIndex; 745 746 public ParameterEnumerable(InputActionState state, ParameterOverride parameter, int mapIndex = -1) 747 { 748 m_State = state; 749 m_Parameter = parameter; 750 m_MapIndex = mapIndex; 751 } 752 753 public ParameterEnumerator GetEnumerator() 754 { 755 return new ParameterEnumerator(m_State, m_Parameter, m_MapIndex); 756 } 757 758 IEnumerator<Parameter> IEnumerable<Parameter>.GetEnumerator() 759 { 760 return GetEnumerator(); 761 } 762 763 IEnumerator IEnumerable.GetEnumerator() 764 { 765 return GetEnumerator(); 766 } 767 } 768 769 private struct ParameterEnumerator : IEnumerator<Parameter> 770 { 771 private InputActionState m_State; 772 private int m_MapIndex; 773 private int m_BindingCurrentIndex; 774 private int m_BindingEndIndex; 775 private int m_InteractionCurrentIndex; 776 private int m_InteractionEndIndex; 777 private int m_ProcessorCurrentIndex; 778 private int m_ProcessorEndIndex; 779 780 private InputBinding m_BindingMask; 781 private Type m_ObjectType; 782 private string m_ParameterName; 783 private bool m_MayBeInteraction; 784 private bool m_MayBeProcessor; 785 private bool m_MayBeComposite; 786 private bool m_CurrentBindingIsComposite; 787 private object m_CurrentObject; 788 private FieldInfo m_CurrentParameter; 789 790 public ParameterEnumerator(InputActionState state, ParameterOverride parameter, int mapIndex = -1) 791 : this() 792 { 793 m_State = state; 794 m_ParameterName = parameter.parameter; 795 m_MapIndex = mapIndex; 796 m_ObjectType = parameter.objectType; 797 m_MayBeComposite = m_ObjectType == null || typeof(InputBindingComposite).IsAssignableFrom(m_ObjectType); 798 m_MayBeProcessor = m_ObjectType == null || typeof(InputProcessor).IsAssignableFrom(m_ObjectType); 799 m_MayBeInteraction = m_ObjectType == null || typeof(IInputInteraction).IsAssignableFrom(m_ObjectType); 800 m_BindingMask = parameter.bindingMask; 801 Reset(); 802 } 803 804 private bool MoveToNextBinding() 805 { 806 // Find a binding that matches our mask. 807 while (true) 808 { 809 ++m_BindingCurrentIndex; 810 if (m_BindingCurrentIndex >= m_BindingEndIndex) 811 return false; // Reached the end. 812 813 ref var binding = ref m_State.GetBinding(m_BindingCurrentIndex); 814 ref var bindingState = ref m_State.GetBindingState(m_BindingCurrentIndex); 815 816 // Skip any binding that has no associated objects with parameters. 817 if (bindingState.processorCount == 0 && bindingState.interactionCount == 0 && !binding.isComposite) 818 continue; 819 820 // If we're only looking for composites, skip any binding that isn't one. 821 if (m_MayBeComposite && !m_MayBeProcessor && !m_MayBeInteraction && !binding.isComposite) 822 continue; 823 824 // If we're only looking for processors, skip any that hasn't got any. 825 if (m_MayBeProcessor && !m_MayBeComposite && !m_MayBeInteraction && bindingState.processorCount == 0) 826 continue; 827 828 // If we're only looking for interactions, skip any that hasn't got any. 829 if (m_MayBeInteraction && !m_MayBeComposite && !m_MayBeProcessor && bindingState.interactionCount == 0) 830 continue; 831 832 if (m_BindingMask.Matches(ref binding)) 833 { 834 if (m_MayBeComposite) 835 m_CurrentBindingIsComposite = binding.isComposite; 836 837 // Reset interaction and processor count. 838 m_ProcessorCurrentIndex = bindingState.processorStartIndex - 1; // Minus one to account for first MoveNext(). 839 m_ProcessorEndIndex = bindingState.processorStartIndex + bindingState.processorCount; 840 m_InteractionCurrentIndex = bindingState.interactionStartIndex - 1; // Minus one to account for first MoveNext(). 841 m_InteractionEndIndex = bindingState.interactionStartIndex + bindingState.interactionCount; 842 843 return true; 844 } 845 } 846 } 847 848 private bool MoveToNextInteraction() 849 { 850 while (m_InteractionCurrentIndex < m_InteractionEndIndex) 851 { 852 ++m_InteractionCurrentIndex; 853 if (m_InteractionCurrentIndex == m_InteractionEndIndex) 854 break; 855 var interaction = m_State.interactions[m_InteractionCurrentIndex]; 856 if (FindParameter(interaction)) 857 return true; 858 } 859 return false; 860 } 861 862 private bool MoveToNextProcessor() 863 { 864 while (m_ProcessorCurrentIndex < m_ProcessorEndIndex) 865 { 866 ++m_ProcessorCurrentIndex; 867 if (m_ProcessorCurrentIndex == m_ProcessorEndIndex) 868 break; 869 var processor = m_State.processors[m_ProcessorCurrentIndex]; 870 if (FindParameter(processor)) 871 return true; 872 } 873 return false; 874 } 875 876 private bool FindParameter(object instance) 877 { 878 if (m_ObjectType != null && !m_ObjectType.IsInstanceOfType(instance)) 879 return false; 880 881 var field = instance.GetType().GetField(m_ParameterName, 882 BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase); 883 if (field == null) 884 return false; 885 886 m_CurrentParameter = field; 887 m_CurrentObject = instance; 888 889 return true; 890 } 891 892 public bool MoveNext() 893 { 894 while (true) 895 { 896 if (m_MayBeInteraction && MoveToNextInteraction()) 897 return true; 898 899 if (m_MayBeProcessor && MoveToNextProcessor()) 900 return true; 901 902 if (!MoveToNextBinding()) 903 return false; 904 905 if (m_MayBeComposite && m_CurrentBindingIsComposite) 906 { 907 var compositeIndex = m_State.GetBindingState(m_BindingCurrentIndex).compositeOrCompositeBindingIndex; 908 var composite = m_State.composites[compositeIndex]; 909 if (FindParameter(composite)) 910 return true; 911 } 912 } 913 } 914 915 public unsafe void Reset() 916 { 917 m_CurrentObject = default; 918 m_CurrentParameter = default; 919 m_InteractionCurrentIndex = default; 920 m_InteractionEndIndex = default; 921 m_ProcessorCurrentIndex = default; 922 m_ProcessorEndIndex = default; 923 m_CurrentBindingIsComposite = default; 924 if (m_MapIndex < 0) 925 { 926 m_BindingCurrentIndex = -1; // Account for first MoveNext(). 927 m_BindingEndIndex = m_State.totalBindingCount; 928 } 929 else 930 { 931 m_BindingCurrentIndex = m_State.mapIndices[m_MapIndex].bindingStartIndex - 1; // Account for first MoveNext(). 932 m_BindingEndIndex = m_State.mapIndices[m_MapIndex].bindingStartIndex + m_State.mapIndices[m_MapIndex].bindingCount; 933 } 934 } 935 936 public Parameter Current => new Parameter 937 { 938 instance = m_CurrentObject, 939 field = m_CurrentParameter, 940 bindingIndex = m_BindingCurrentIndex, 941 }; 942 943 object IEnumerator.Current => Current; 944 945 public void Dispose() 946 { 947 } 948 } 949 950 internal struct ParameterOverride 951 { 952 public string objectRegistrationName; // Optional. Such as "hold" or "scale". 953 public string parameter; 954 public InputBinding bindingMask; 955 public PrimitiveValue value; 956 957 public Type objectType => 958 InputProcessor.s_Processors.LookupTypeRegistration(objectRegistrationName) 959 ?? InputInteraction.s_Interactions.LookupTypeRegistration(objectRegistrationName) 960 ?? InputBindingComposite.s_Composites.LookupTypeRegistration(objectRegistrationName); 961 962 public ParameterOverride(string parameterName, InputBinding bindingMask, PrimitiveValue value = default) 963 { 964 var colonIndex = parameterName.IndexOf(':'); 965 if (colonIndex < 0) 966 { 967 objectRegistrationName = null; 968 parameter = parameterName; 969 } 970 else 971 { 972 objectRegistrationName = parameterName.Substring(0, colonIndex); 973 parameter = parameterName.Substring(colonIndex + 1); 974 } 975 this.bindingMask = bindingMask; 976 this.value = value; 977 } 978 979 public ParameterOverride(string objectRegistrationName, string parameterName, InputBinding bindingMask, PrimitiveValue value = default) 980 { 981 this.objectRegistrationName = objectRegistrationName; 982 this.parameter = parameterName; 983 this.bindingMask = bindingMask; 984 this.value = value; 985 } 986 987 // Find the *most specific* override to apply to the given parameter. 988 public static ParameterOverride? Find(InputActionMap actionMap, ref InputBinding binding, string parameterName, string objectRegistrationName) 989 { 990 // Look at level of map. 991 var overrideOnMap = Find(actionMap.m_ParameterOverrides, actionMap.m_ParameterOverridesCount, ref binding, parameterName, 992 objectRegistrationName); 993 994 // Look at level of asset (if present). 995 var asset = actionMap.asset; 996 var overrideOnAsset = asset != null 997 ? Find(asset.m_ParameterOverrides, asset.m_ParameterOverridesCount, ref binding, parameterName, 998 objectRegistrationName) 999 : null; 1000 1001 return PickMoreSpecificOne(overrideOnMap, overrideOnAsset); 1002 } 1003 1004 private static ParameterOverride? Find(ParameterOverride[] overrides, int overrideCount, 1005 ref InputBinding binding, string parameterName, string objectRegistrationName) 1006 { 1007 ParameterOverride? result = null; 1008 for (var i = 0; i < overrideCount; ++i) 1009 { 1010 ref var current = ref overrides[i]; 1011 1012 if (!string.Equals(parameterName, current.parameter, StringComparison.OrdinalIgnoreCase)) 1013 continue; // Different parameter name. 1014 1015 if (!current.bindingMask.Matches(binding)) 1016 continue; 1017 1018 if (current.objectRegistrationName != null && !string.Equals(current.objectRegistrationName, objectRegistrationName, 1019 StringComparison.OrdinalIgnoreCase)) 1020 continue; 1021 1022 if (result == null) 1023 { 1024 // First match. 1025 result = current; 1026 } 1027 else 1028 { 1029 // Already have a match. See which one is more specific. 1030 result = PickMoreSpecificOne(result, current); 1031 } 1032 } 1033 return result; 1034 } 1035 1036 private static ParameterOverride? PickMoreSpecificOne(ParameterOverride? first, ParameterOverride? second) 1037 { 1038 if (first == null) 1039 return second; 1040 if (second == null) 1041 return first; 1042 1043 // Having an objectRegistrationName always wins vs not having one. 1044 if (first.Value.objectRegistrationName != null && second.Value.objectRegistrationName == null) 1045 return first; 1046 if (second.Value.objectRegistrationName != null && first.Value.objectRegistrationName == null) 1047 return second; 1048 1049 // Targeting a specific path always wins vs not doing so. 1050 if (first.Value.bindingMask.effectivePath != null && second.Value.bindingMask.effectivePath == null) 1051 return first; 1052 if (second.Value.bindingMask.effectivePath != null && first.Value.bindingMask.effectivePath == null) 1053 return second; 1054 1055 // Targeting a specific actions always wins vs not doing so. 1056 if (first.Value.bindingMask.action != null && second.Value.bindingMask.action == null) 1057 return first; 1058 if (second.Value.bindingMask.action != null && first.Value.bindingMask.action == null) 1059 return second; 1060 1061 // Undecided. First wins by default. 1062 return first; 1063 } 1064 } 1065 } 1066}