| #if MLA_INPUT_SYSTEM |
| using System; |
| using System.Collections.Generic; |
| using Unity.Collections; |
| using Unity.MLAgents.Actuators; |
| using Unity.MLAgents.Policies; |
| using UnityEngine; |
| using UnityEngine.Assertions; |
| using UnityEngine.InputSystem; |
| using UnityEngine.InputSystem.Controls; |
| using UnityEngine.InputSystem.LowLevel; |
| using UnityEngine.InputSystem.Layouts; |
| using UnityEngine.InputSystem.Utilities; |
| #if UNITY_EDITOR |
| using UnityEditor; |
| #endif |
|
|
| namespace Unity.MLAgents.Input |
| { |
| |
| |
| |
| |
| [RequireComponent(typeof(PlayerInput), typeof(IInputActionAssetProvider))] |
| [AddComponentMenu("ML Agents/Input Actuator", (int)MenuGroup.Actuators)] |
| [UnityEngine.Scripting.APIUpdating.MovedFrom("Unity.MLAgents.Extensions.Input")] |
| public class InputActuatorComponent : ActuatorComponent |
| { |
| InputActionAsset m_InputAsset; |
| IInputActionCollection2 m_AssetCollection; |
| PlayerInput m_PlayerInput; |
| BehaviorParameters m_BehaviorParameters; |
| IActuator[] m_Actuators; |
| InputDevice m_Device; |
|
|
| |
| |
| |
| public static readonly Dictionary<Type, Type> controlTypeToAdaptorType = new Dictionary<Type, Type> |
| { |
| { typeof(Vector2Control), typeof(Vector2InputActionAdaptor) }, |
| { typeof(ButtonControl), typeof(ButtonInputActionAdaptor) }, |
| { typeof(IntegerControl), typeof(IntegerInputActionAdaptor) }, |
| { typeof(AxisControl), typeof(FloatInputActionAdaptor) }, |
| { typeof(DoubleControl), typeof(DoubleInputActionAdaptor) } |
| }; |
|
|
| string m_LayoutName; |
| [SerializeField] |
| ActionSpec m_ActionSpec; |
| InputControlScheme m_ControlScheme; |
|
|
| public const string mlAgentsLayoutFormat = "MLAT"; |
| public const string mlAgentsLayoutName = "MLAgentsLayout"; |
| public const string mlAgentsControlSchemeName = "ml-agents"; |
|
|
| |
| public override ActionSpec ActionSpec |
| { |
| get |
| { |
| #if UNITY_EDITOR |
| if (!EditorApplication.isPlaying && m_ActionSpec.NumContinuousActions == 0 |
| && m_ActionSpec.BranchSizes == null |
| || m_ActionSpec.BranchSizes.Length == 0) |
| { |
| FindNeededComponents(); |
| var actuators = CreateActuatorsFromMap(m_InputAsset.FindActionMap(m_PlayerInput.defaultActionMap), |
| m_BehaviorParameters, |
| null, |
| InputActuatorEventContext.s_EditorContext); |
| m_ActionSpec = CombineActuatorActionSpecs(actuators); |
| } |
| #endif |
| return m_ActionSpec; |
| } |
| } |
|
|
| void OnDisable() |
| { |
| CleanupActionAsset(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| public override IActuator[] CreateActuators() |
| { |
| FindNeededComponents(); |
| var collection = m_AssetCollection ?? m_InputAsset; |
| collection.Disable(); |
| var inputActionMap = m_InputAsset.FindActionMap(m_PlayerInput.defaultActionMap); |
|
|
| RegisterLayoutBuilder(inputActionMap, m_LayoutName); |
| m_Device = InputSystem.AddDevice(m_LayoutName); |
|
|
| var context = new InputActuatorEventContext(inputActionMap.actions.Count, m_Device); |
| m_Actuators = CreateActuatorsFromMap(inputActionMap, m_BehaviorParameters, m_Device, context); |
|
|
| UpdateDeviceBinding(m_BehaviorParameters.IsInHeuristicMode()); |
| inputActionMap.Enable(); |
|
|
| m_ActionSpec = CombineActuatorActionSpecs(m_Actuators); |
| collection.Enable(); |
| return m_Actuators; |
| } |
|
|
| static ActionSpec CombineActuatorActionSpecs(IActuator[] actuators) |
| { |
| var specs = new ActionSpec[actuators.Length]; |
| for (var i = 0; i < actuators.Length; i++) |
| { |
| specs[i] = actuators[i].ActionSpec; |
| } |
| return ActionSpec.Combine(specs); |
| } |
|
|
| internal static IActuator[] CreateActuatorsFromMap(InputActionMap inputActionMap, |
| BehaviorParameters behaviorParameters, |
| InputDevice inputDevice, |
| InputActuatorEventContext context) |
| { |
| var actuators = new IActuator[inputActionMap.actions.Count]; |
| for (var i = 0; i < inputActionMap.actions.Count; i++) |
| { |
| var action = inputActionMap.actions[i]; |
| var actionLayout = InputSystem.LoadLayout(action.expectedControlType); |
| var adaptor = (IRLActionInputAdaptor)Activator.CreateInstance(controlTypeToAdaptorType[actionLayout.type]); |
| actuators[i] = new InputActionActuator(inputDevice, behaviorParameters, action, adaptor, context); |
|
|
| |
| |
| var path = $"{inputDevice?.path}{InputControlPath.Separator}{action.name}"; |
| action.AddBinding(path, |
| action.interactions, |
| action.processors, |
| mlAgentsControlSchemeName); |
| action.bindingMask = InputBinding.MaskByGroup(mlAgentsControlSchemeName); |
| } |
| return actuators; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| internal void UpdateDeviceBinding(bool isInHeuristicMode) |
| { |
| if (ReferenceEquals(m_Device, null)) |
| { |
| return; |
| } |
| var collection = m_AssetCollection ?? m_InputAsset; |
| m_ControlScheme = CreateControlScheme(m_Device, isInHeuristicMode, m_InputAsset); |
| if (m_InputAsset.FindControlSchemeIndex(m_ControlScheme.name) != -1) |
| { |
| m_InputAsset.RemoveControlScheme(m_ControlScheme.name); |
| } |
|
|
| if (!isInHeuristicMode) |
| { |
| var inputActionMap = m_InputAsset.FindActionMap(m_PlayerInput.defaultActionMap); |
| m_InputAsset.AddControlScheme(m_ControlScheme); |
| collection.bindingMask = InputBinding.MaskByGroup(m_ControlScheme.bindingGroup); |
| collection.devices = new ReadOnlyArray<InputDevice>(new[] { m_Device }); |
| inputActionMap.bindingMask = collection.bindingMask; |
| inputActionMap.devices = collection.devices; |
| } |
| else |
| { |
| var inputActionMap = m_InputAsset.FindActionMap(m_PlayerInput.defaultActionMap); |
| collection.bindingMask = null; |
| collection.devices = InputSystem.devices; |
| inputActionMap.devices = InputSystem.devices; |
| inputActionMap.bindingMask = null; |
| } |
| collection.Enable(); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| internal static InputControlScheme CreateControlScheme(InputControl device, |
| bool isInHeuristicMode, |
| InputActionAsset asset) |
| { |
| var deviceRequirements = new List<InputControlScheme.DeviceRequirement> |
| { |
| new InputControlScheme.DeviceRequirement |
| { |
| controlPath = InputBinding.Separator + mlAgentsLayoutName |
| } |
| }; |
|
|
| if (isInHeuristicMode) |
| { |
| for (var i = 0; i < asset.controlSchemes.Count; i++) |
| { |
| var scheme = asset.controlSchemes[i]; |
| for (var ii = 0; ii < scheme.deviceRequirements.Count; ii++) |
| { |
| deviceRequirements.Add(scheme.deviceRequirements[ii]); |
| } |
| } |
| } |
|
|
| var inputControlScheme = new InputControlScheme( |
| mlAgentsControlSchemeName, |
| deviceRequirements); |
|
|
| return inputControlScheme; |
| } |
|
|
| |
| |
| |
| |
| |
| internal static void RegisterLayoutBuilder(InputActionMap defaultMap, string layoutName) |
| { |
| if (InputSystem.LoadLayout(layoutName) == null) |
| { |
| InputSystem.RegisterLayoutBuilder(() => |
| { |
| |
| var builder = new InputControlLayout.Builder() |
| .WithName(layoutName) |
| .WithFormat(mlAgentsLayoutFormat); |
| for (var i = 0; i < defaultMap.actions.Count; i++) |
| { |
| var action = defaultMap.actions[i]; |
| builder.AddControl(action.name) |
| .WithLayout(action.expectedControlType); |
| } |
| return builder.Build(); |
| }, layoutName); |
| } |
| } |
|
|
| internal void FindNeededComponents() |
| { |
| if (m_InputAsset == null) |
| { |
| var assetProvider = GetComponent<IInputActionAssetProvider>(); |
| Assert.IsNotNull(assetProvider); |
| (m_InputAsset, m_AssetCollection) = assetProvider.GetInputActionAsset(); |
| Assert.IsNotNull(m_InputAsset, "An InputActionAsset could not be found on IInputActionAssetProvider or PlayerInput."); |
| } |
| if (m_PlayerInput == null) |
| { |
| m_PlayerInput = GetComponent<PlayerInput>(); |
| Assert.IsNotNull(m_PlayerInput, "PlayerInput component could not be found on this GameObject."); |
| } |
|
|
| if (m_BehaviorParameters == null) |
| { |
| m_BehaviorParameters = GetComponent<BehaviorParameters>(); |
| Assert.IsNotNull(m_BehaviorParameters, "BehaviorParameters were not on the current GameObject."); |
| m_BehaviorParameters.OnPolicyUpdated += UpdateDeviceBinding; |
| m_LayoutName = mlAgentsLayoutName + m_BehaviorParameters.BehaviorName; |
| } |
| } |
|
|
| internal void CleanupActionAsset() |
| { |
| InputSystem.RemoveLayout(mlAgentsLayoutName); |
| if (!ReferenceEquals(m_Device, null)) |
| { |
| InputSystem.RemoveDevice(m_Device); |
| } |
|
|
| if (!ReferenceEquals(m_InputAsset, null) |
| && m_InputAsset.FindControlSchemeIndex(mlAgentsControlSchemeName) != -1) |
| { |
| m_InputAsset.RemoveControlScheme(mlAgentsControlSchemeName); |
| } |
|
|
| if (m_Actuators != null) |
| { |
| Array.Clear(m_Actuators, 0, m_Actuators.Length); |
| } |
|
|
| if (!ReferenceEquals(m_BehaviorParameters, null)) |
| { |
| m_BehaviorParameters.OnPolicyUpdated -= UpdateDeviceBinding; |
| } |
|
|
| m_InputAsset = null; |
| m_PlayerInput = null; |
| m_BehaviorParameters = null; |
| m_Device = null; |
| } |
|
|
| int m_ActuatorsWrittenToEvent; |
| NativeArray<byte> m_InputBufferForFrame; |
| InputEventPtr m_InputEventPtrForFrame; |
| public InputEventPtr GetEventForFrame() |
| { |
| #if UNITY_EDITOR |
| if (!EditorApplication.isPlaying) |
| { |
| return new InputEventPtr(); |
| } |
| #endif |
| if (m_ActuatorsWrittenToEvent % m_Actuators.Length == 0 || !m_InputEventPtrForFrame.valid) |
| { |
| m_ActuatorsWrittenToEvent = 0; |
| m_InputEventPtrForFrame = new InputEventPtr(); |
| m_InputBufferForFrame = StateEvent.From(m_Device, out m_InputEventPtrForFrame); |
| } |
|
|
| return m_InputEventPtrForFrame; |
| } |
|
|
| public void EventProcessedInFrame() |
| { |
| #if UNITY_EDITOR |
| if (!EditorApplication.isPlaying) |
| { |
| return; |
| } |
| #endif |
| m_ActuatorsWrittenToEvent++; |
| if (m_ActuatorsWrittenToEvent == m_Actuators.Length && m_InputEventPtrForFrame.valid) |
| { |
| InputSystem.QueueEvent(m_InputEventPtrForFrame); |
| m_InputBufferForFrame.Dispose(); |
| } |
| } |
| } |
| } |
| #endif // MLA_INPUT_SYSTEM |
|
|