using System; using System.Linq; using Unity.WebRTC; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Utilities; using UnityEngine.InputSystem.Users; using Unity.RenderStreaming.InputSystem; using Inputs = UnityEngine.InputSystem.InputSystem; using InputRemoting = Unity.RenderStreaming.InputSystem.InputRemoting; namespace Unity.RenderStreaming { /// /// Represents a separate player in the game complete with a set of actions exclusive /// to the player and a set of paired device. /// It is the simple version of UnityEngine.InputSystem.PlayerInput that removing dependency of InputControlScheme. /// [AddComponentMenu("Render Streaming/Input Receiver")] public class InputReceiver : InputChannelReceiverBase { /// /// /// public override event Action onDeviceChange; /// /// /// public InputActionAsset actions { get { if (!m_ActionsInitialized && gameObject.activeSelf) InitializeActions(); return m_Actions; } set { if (m_Actions == value) return; // Make sure that if we already have actions, they get disabled. if (m_Actions != null) { m_Actions.Disable(); if (m_Enabled) UninitializeActions(); } m_Actions = value; if (m_Enabled) { //ClearCaches(); AssignUserAndDevices(); InitializeActions(); if (m_InputActive) ActivateInput(); } } } /// /// /// public bool inputIsActive => m_InputActive; /// /// /// public InputUser user => m_InputUser; /// /// /// public ReadOnlyArray devices => m_InputUser.pairedDevices; /// /// /// public InputActionMap currentActionMap { get => m_CurrentActionMap; set { m_CurrentActionMap?.Disable(); m_CurrentActionMap = value; m_CurrentActionMap?.Enable(); } } /// /// /// public string defaultActionMap { get => m_DefaultActionMap; set => m_DefaultActionMap = value; } /// /// /// public ReadOnlyArray actionEvents { get => m_ActionEvents; set { if (m_Enabled) UninitializeActions(); m_ActionEvents = value.ToArray(); if (m_Enabled) InitializeActions(); } } /// /// /// protected virtual void OnEnable() { m_Enabled = true; onDeviceChange += OnDeviceChange; //AssignPlayerIndex(); InitializeActions(); AssignUserAndDevices(); ActivateInput(); } /// /// /// protected virtual void OnDisable() { m_Enabled = false; onDeviceChange -= OnDeviceChange; DeactivateInput(); UnassignUserAndDevices(); UninitializeActions(); } /// /// /// public void ActivateInput() { m_InputActive = true; // If we have no current action map but there's a default // action map, make it current. if (m_CurrentActionMap == null && m_Actions != null && !string.IsNullOrEmpty(m_DefaultActionMap)) SwitchCurrentActionMap(m_DefaultActionMap); else m_CurrentActionMap?.Enable(); } /// /// /// public void DeactivateInput() { m_CurrentActionMap?.Disable(); m_InputActive = false; } /// /// /// /// public void SwitchCurrentActionMap(string mapNameOrId) { // Must be enabled. if (!m_Enabled) { Debug.LogError($"Cannot switch to actions '{mapNameOrId}'; input is not enabled", this); return; } // Must have actions. if (m_Actions == null) { Debug.LogError($"Cannot switch to actions '{mapNameOrId}'; no actions set on PlayerInput", this); return; } // Must have map. var actionMap = m_Actions.FindActionMap(mapNameOrId); if (actionMap == null) { Debug.LogError($"Cannot find action map '{mapNameOrId}' in actions '{m_Actions}'", this); return; } currentActionMap = actionMap; } /// /// /// /// public void PerformPairingWithDevice(InputDevice device) { m_InputUser = InputUser.PerformPairingWithDevice(device, m_InputUser); } /// /// /// public void PerformPairingWithAllLocalDevices() { foreach (var device in Inputs.devices.Where(_ => !_.remote)) { PerformPairingWithDevice(device); } } /// /// /// /// public void UnpairDevices(InputDevice device) { if (!m_InputUser.valid) return; m_InputUser.UnpairDevice(device); } /// /// /// /// public override void SetChannel(string connectionId, RTCDataChannel channel) { if (channel == null) { Dispose(); } else { receiver = new Receiver(channel); receiver.onDeviceChange += onDeviceChange; receiverInput = new InputRemoting(receiver); subscriberDisposer = receiverInput.Subscribe(receiverInput); receiverInput.StartSending(); } base.SetChannel(connectionId, channel); } /// /// /// /// Texture Size. /// Region of the texture in world coordinate system. public void CalculateInputRegion(Vector2Int size, Rect region) { receiver.CalculateInputRegion(new Rect(Vector2.zero, size), region); } /// /// /// /// public void SetEnableInputPositionCorrection(bool enabled) { receiver.EnableInputPositionCorrection = enabled; } /// /// /// protected virtual void OnDestroy() { Dispose(); } protected virtual void Dispose() { receiverInput?.StopSending(); subscriberDisposer?.Dispose(); receiver?.Dispose(); receiver = null; } [Tooltip("Input actions associated with the player.")] [SerializeField] internal InputActionAsset m_Actions; [SerializeField] internal PlayerInput.ActionEvent[] m_ActionEvents; [SerializeField] internal string m_DefaultActionMap; [NonSerialized] internal InputActionMap m_CurrentActionMap; [NonSerialized] private bool m_InputActive; [NonSerialized] private bool m_Enabled; [NonSerialized] private bool m_ActionsInitialized; [NonSerialized] private InputUser m_InputUser; [NonSerialized] private Receiver receiver; [NonSerialized] private InputRemoting receiverInput; [NonSerialized] private IDisposable subscriberDisposer; private void AssignUserAndDevices() { // If we already have a user at this point, clear out all its paired devices // to start the pairing process from scratch. if (m_InputUser.valid) m_InputUser.UnpairDevices(); // All our input goes through actions so there's no point setting // anything up if we have none. if (m_Actions == null) { // Make sure user is invalid. m_InputUser = new InputUser(); return; } m_InputUser = InputUser.CreateUserWithoutPairedDevices(); // If we don't have a valid user at this point, we don't have any paired devices. if (m_InputUser.valid) m_InputUser.AssociateActionsWithUser(actions); } private void UnassignUserAndDevices() { m_InputUser.UnpairDevicesAndRemoveUser(); } private void InitializeActions() { if (m_ActionsInitialized) return; if (m_Actions == null) return; var oldActions = m_Actions; m_Actions = Instantiate(m_Actions); for (var actionMap = 0; actionMap < oldActions.actionMaps.Count; actionMap++) { for (var binding = 0; binding < oldActions.actionMaps[actionMap].bindings.Count; binding++) m_Actions.actionMaps[actionMap].ApplyBindingOverride(binding, oldActions.actionMaps[actionMap].bindings[binding]); } // Hook up all action events. if (m_ActionEvents != null) { foreach (var actionEvent in m_ActionEvents) { var id = actionEvent.actionId; if (string.IsNullOrEmpty(id)) continue; // Find action for event. var action = m_Actions.FindAction(id); if (action != null) { ////REVIEW: really wish we had a single callback action.performed += actionEvent.Invoke; action.canceled += actionEvent.Invoke; action.started += actionEvent.Invoke; } else { // Cannot find action. Log error. if (!string.IsNullOrEmpty(actionEvent.actionName)) { // We have an action name. Show in message. Debug.LogError( $"Cannot find action '{actionEvent.actionName}' with ID '{actionEvent.actionId}' in '{m_Actions}", this); } else { // We have no action name. Best we have is ID. Debug.LogError( $"Cannot find action with ID '{actionEvent.actionId}' in '{m_Actions}", this); } } } } m_ActionsInitialized = true; } private void UninitializeActions() { if (!m_ActionsInitialized) return; if (m_Actions == null) return; //UninstallOnActionTriggeredHook(); if (m_ActionEvents != null) { foreach (var actionEvent in m_ActionEvents) { var id = actionEvent.actionId; if (string.IsNullOrEmpty(id)) continue; // Find action for event. var action = m_Actions.FindAction(id); if (action != null) { ////REVIEW: really wish we had a single callback action.performed -= actionEvent.Invoke; action.canceled -= actionEvent.Invoke; action.started -= actionEvent.Invoke; } } } m_CurrentActionMap = null; m_ActionsInitialized = false; } protected virtual void OnDeviceChange(InputDevice device, InputDeviceChange change) { switch (change) { case InputDeviceChange.Added: PerformPairingWithDevice(device); return; case InputDeviceChange.Removed: UnpairDevices(device); return; } } } }