Skip to content

Commit

Permalink
FIX: Errors are thrown when assigning Input Actions Asset that contai…
Browse files Browse the repository at this point in the history
…ns a custom InputProcessor or InputBindingComposite as Project-wide Input Actions (ISXB-856) (#1958)

* Add reflection to load custom InputProcessor, IInputInteraction and InputBindingComposite
  • Loading branch information
bmalrat authored Aug 19, 2024
1 parent cf28d7d commit ab2c3d7
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 53 deletions.
58 changes: 13 additions & 45 deletions Assets/Samples/CustomComposite/CustomComposite.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,61 +27,29 @@
// our composite will not be shown as our value type (Vector2) is
// incompatible with the value type of Axis (float).
//
// Also, we need to register our composite with the input system. And we
// want to do it in a way that makes the composite visible in the action
// editor of the input system.
//
// For that to happen, we need to call InputSystem.RegisterBindingComposite
// sometime during startup. We make that happen by using [InitializeOnLoad]
// in the editor and [RuntimeInitializeOnLoadMethod] in the player.
#if UNITY_EDITOR
[InitializeOnLoad]
#endif
// We can customize the way display strings are formed for our composite by
// annotating it with DisplayStringFormatAttribute. The string is simply a
// list with elements to be replaced enclosed in curly braces. Everything
// outside those will taken verbatim. The fragments inside the curly braces
// in this case refer to the binding composite parts by name. Each such
// instance is replaced with the display text for the corresponding
// part binding.
//
// NOTE: We don't supply a name for the composite here. The default logic
// will take the name of the type ("CustomComposite" in our case)
// and snip off "Composite" if used as a suffix (which is the case
// for us) and then use that as the name. So in our case, we are
// registering a composite called "Custom" here.
//
// If we were to use our composite with the AddCompositeBinding API,
// for example, it would look like this:
//
// myAction.AddCompositeBinding("Custom")
// .With("Stick", "<Gamepad>/leftStick")
// .With("Multiplier", "<Gamepad>/rightTrigger");
[DisplayStringFormat("{multiplier}*{stick}")]
public class CustomComposite : InputBindingComposite<Vector2>
{
// In the editor, the static class constructor will be called on startup
// because of [InitializeOnLoad].
#if UNITY_EDITOR
static CustomComposite()
{
// Trigger our RegisterBindingComposite code in the editor.
Initialize();
}

#endif

// In the player, [RuntimeInitializeOnLoadMethod] will make sure our
// initialization code gets called during startup.
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Initialize()
{
// This registers the composite with the input system. After calling this
// method, we can have bindings reference the composite. Also, the
// composite will show up in the action editor.
//
// NOTE: We don't supply a name for the composite here. The default logic
// will take the name of the type ("CustomComposite" in our case)
// and snip off "Composite" if used as a suffix (which is the case
// for us) and then use that as the name. So in our case, we are
// registering a composite called "Custom" here.
//
// If we were to use our composite with the AddCompositeBinding API,
// for example, it would look like this:
//
// myAction.AddCompositeBinding("Custom")
// .With("Stick", "<Gamepad>/leftStick")
// .With("Multiplier", "<Gamepad>/rightTrigger");
InputSystem.RegisterBindingComposite<CustomComposite>();
}

// So, we need two parts for our composite. The part that delivers the stick
// value and the part that delivers the axis multiplier. Note that each part
// may be bound to multiple controls. The input system handles that for us
Expand Down
3 changes: 2 additions & 1 deletion Packages/com.unity.inputsystem/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ however, it has to be formatted properly to pass verification tests.
- Use `ProfilerMarker` instead of `Profiler.BeginSample` and `Profiler.EndSample` when appropriate to enable recording of profiling data.

### Added
- Added tests for Input Action Editor UI for managing action maps (List, create, rename, delete) (ISX-2087)
- Added tests for Input Action Editor UI for managing action maps (List, create, rename, delete) (ISX-2087)
- Added automatic loading of custom extensions of InputProcessor, InputInteraction and InputBindingComposite [ISXB-856]](https://issuetracker.unity3d.com/product/unity/issues/guid/ISXB-856).

## [1.10.0] - 2024-07-24

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ namespace UnityEngine.InputSystem
/// </code>
/// </example>
///
/// Custom interactions can be registered using <see cref="InputSystem.RegisterInteraction"/>. This can be
/// Custom interactions are automatically registered by reflection but it can also be manually registered using <see cref="InputSystem.RegisterInteraction"/>. 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:
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,8 +279,8 @@ internal static string GetDisplayFormatString(string composite)
///
/// The set of composites available in the system is extensible. While some composites are
/// such as <see cref="Composites.Vector2Composite"/> and <see cref="Composites.ButtonWithOneModifier"/>
/// are available out of the box, new composites can be implemented by anyone and simply be
/// registered with <see cref="InputSystem.RegisterBindingComposite{T}"/>.
/// are available out of the box, new composites can be implemented by anyone and simply be autodiscover
/// or manually registered with <see cref="InputSystem.RegisterBindingComposite{T}"/>.
///
/// See the "Custom Composite" sample (can be installed from package manager UI) for a detailed example
/// of how to create a custom composite.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,6 @@ public enum CachingPolicy
///
/// <example>
/// <code>
/// // To register the processor, call
/// //
/// // InputSystem.RegisterProcessor&lt;ScalingProcessor&gt;("scale");
/// //
/// public class ScalingProcessor : InputProcessor&lt;float&gt;
/// {
/// // This field can be set as a parameter. See examples below.
Expand Down
61 changes: 61 additions & 0 deletions Packages/com.unity.inputsystem/InputSystem/InputManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using Unity.Collections;
Expand Down Expand Up @@ -68,6 +69,7 @@ internal partial class InputManager
static readonly ProfilerMarker k_InputTryFindMatchingControllerMarker = new ProfilerMarker("InputSystem.TryFindMatchingControlLayout");
static readonly ProfilerMarker k_InputAddDeviceMarker = new ProfilerMarker("InputSystem.AddDevice");
static readonly ProfilerMarker k_InputRestoreDevicesAfterReloadMarker = new ProfilerMarker("InputManager.RestoreDevicesAfterDomainReload");
static readonly ProfilerMarker k_InputRegisterCustomTypesMarker = new ProfilerMarker("InputManager.RegisterCustomTypes");

public InputMetrics metrics
{
Expand Down Expand Up @@ -1965,6 +1967,65 @@ internal void InitializeData()
composites.AddTypeRegistration("ButtonWithTwoModifiers", typeof(ButtonWithTwoModifiers));
composites.AddTypeRegistration("OneModifier", typeof(OneModifierComposite));
composites.AddTypeRegistration("TwoModifiers", typeof(TwoModifiersComposite));

// Register custom types by reflection
RegisterCustomTypes();
}

void RegisterCustomTypes(Type[] types)
{
foreach (Type type in types)
{
if (!type.IsClass
|| type.IsAbstract
|| type.IsGenericType)
continue;
if (typeof(InputProcessor).IsAssignableFrom(type))
{
InputSystem.RegisterProcessor(type);
}
else if (typeof(IInputInteraction).IsAssignableFrom(type))
{
InputSystem.RegisterInteraction(type);
}
else if (typeof(InputBindingComposite).IsAssignableFrom(type))
{
InputSystem.RegisterBindingComposite(type, null);
}
}
}

void RegisterCustomTypes()
{
k_InputRegisterCustomTypesMarker.Begin();

var inputSystemAssembly = typeof(InputProcessor).Assembly;
var inputSystemName = inputSystemAssembly.GetName().Name;
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
try
{
// exclude InputSystem assembly which should be loaded first
if (assembly == inputSystemAssembly) continue;

// Only register types from assemblies that reference InputSystem
foreach (var referencedAssembly in assembly.GetReferencedAssemblies())
{
if (referencedAssembly.Name == inputSystemName)
{
RegisterCustomTypes(assembly.GetTypes());
break;
}
}
}
catch (ReflectionTypeLoadException)
{
continue;
}
}

k_InputRegisterCustomTypesMarker.End();
}

internal void InstallRuntime(IInputRuntime runtime)
Expand Down

0 comments on commit ab2c3d7

Please sign in to comment.