Maze / Library /PackageCache /com.unity.inputsystem@1.6.1 /InputSystem /Plugins /PlayerInput /PlayerInput.cs
| using System; | |
| using System.Collections.Generic; | |
| using UnityEngine.Events; | |
| using UnityEngine.InputSystem.LowLevel; | |
| using UnityEngine.InputSystem.Users; | |
| using UnityEngine.InputSystem.Utilities; | |
| using UnityEngine.InputSystem.UI; | |
| ////TODO: add support for keeping a player's InputUser alive and reconnecting back to it | |
| ////TODO: when joining is *off*, allow auto-switching even in multiplayer | |
| ////TODO: differentiate not only by already paired devices but rather take control schemes into account; allow two players to be on the same | |
| //// device as long as they are using different control schemes | |
| ////TODO: allow PlayerInput to be set up in a way where it's in an unpaired/non-functional state and expects additional configuration | |
| ////REVIEW: callback behaviors have been very confusing for users; simplify&clarify this | |
| ////REVIEW: having everything coupled to component enable/disable is quite restrictive; can we allow PlayerInputs | |
| //// to be disabled without them leaving the game? would help when wanting to keep players around in the background | |
| //// and only temporarily disable them | |
| ////TODO: add support for "continuous" callbacks | |
| ////TODO: add event for control scheme switches | |
| ////TODO: add ability to name players | |
| ////TODO: refresh caches when asset is modified at runtime | |
| ////TODO: handle required actions ahead of time so that we catch it if a device matches by type but doesn't otherwise | |
| ////TODO: handle case of control scheme not having any devices in its requirements | |
| ////TODO: add method to pass an object implementing a generated action interface (IXXXActions) and have it hooked up automatically | |
| //// (or maybe look for implementation on components in same object?) | |
| ////TODO: warn if control schemes have no device requirements | |
| ////FIXME: why can't I join with a mouse left click? | |
| namespace UnityEngine.InputSystem | |
| { | |
| /// <summary> | |
| /// Represents a separate player in the game complete with a set of actions exclusive | |
| /// to the player and a set of paired device. | |
| /// </summary> | |
| /// <remarks> | |
| /// PlayerInput is a high-level wrapper around much of the input system's functionality | |
| /// which is meant to help getting set up with the new input system quickly. It takes | |
| /// care of <see cref="InputAction"/> bookkeeping and has a custom UI(requires the "Unity UI" package) to help | |
| /// setting up input. | |
| /// | |
| /// The component supports local multiplayer implicitly. Each PlayerInput instance | |
| /// represents a distinct user with its own set of devices and actions. To orchestrate | |
| /// player management and facilitate mechanics such as joining by device activity, use | |
| /// <see cref="UnityEngine.InputSystem.PlayerInputManager"/>. | |
| /// | |
| /// The way PlayerInput notifies script code of events is determined by <see cref="notificationBehavior"/>. | |
| /// By default, this is set to <see cref="UnityEngine.InputSystem.PlayerNotifications.SendMessages"/> which will use | |
| /// <see cref="GameObject.SendMessage(string,object)"/> to send messages to the <see cref="GameObject"/> | |
| /// that PlayerInput sits on. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// // Component to sit next to PlayerInput. | |
| /// [RequireComponent(typeof(PlayerInput))] | |
| /// public class MyPlayerLogic : MonoBehaviour | |
| /// { | |
| /// public GameObject projectilePrefab; | |
| /// | |
| /// private Vector2 m_Look; | |
| /// private Vector2 m_Move; | |
| /// private bool m_Fire; | |
| /// | |
| /// // 'Fire' input action has been triggered. For 'Fire' we want continuous | |
| /// // action (that is, firing) while the fire button is held such that the action | |
| /// // gets triggered repeatedly while the button is down. We can easily set this | |
| /// // up by having a "Press" interaction on the button and setting it to repeat | |
| /// // at fixed intervals. | |
| /// public void OnFire() | |
| /// { | |
| /// Instantiate(projectilePrefab); | |
| /// } | |
| /// | |
| /// // 'Move' input action has been triggered. | |
| /// public void OnMove(InputValue value) | |
| /// { | |
| /// m_Move = value.Get<Vector2>(); | |
| /// } | |
| /// | |
| /// // 'Look' input action has been triggered. | |
| /// public void OnLook(InputValue value) | |
| /// { | |
| /// m_Look = value.Get<Vector2>(); | |
| /// } | |
| /// | |
| /// public void OnUpdate() | |
| /// { | |
| /// // Update transform from m_Move and m_Look | |
| /// } | |
| /// } | |
| /// </code> | |
| /// </example> | |
| /// | |
| /// It is also possible to use the polling API of <see cref="InputAction"/>s (see | |
| /// <see cref="InputAction.triggered"/> and <see cref="InputAction.ReadValue{TValue}"/>) | |
| /// in combination with PlayerInput. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// // Component to sit next to PlayerInput. | |
| /// [RequireComponent(typeof(PlayerInput))] | |
| /// public class MyPlayerLogic : MonoBehaviour | |
| /// { | |
| /// public GameObject projectilePrefab; | |
| /// | |
| /// private PlayerInput m_PlayerInput; | |
| /// private InputAction m_LookAction; | |
| /// private InputAction m_MoveAction; | |
| /// private InputAction m_FireAction; | |
| /// | |
| /// public void OnUpdate() | |
| /// { | |
| /// // First update we look up all the data we need. | |
| /// // NOTE: We don't do this in OnEnable as PlayerInput itself performing some | |
| /// // initialization work in OnEnable. | |
| /// if (m_PlayerInput == null) | |
| /// { | |
| /// m_PlayerInput = GetComponent<PlayerInput>(); | |
| /// m_FireAction = m_PlayerInput.actions["fire"]; | |
| /// m_LookAction = m_PlayerInput.actions["look"]; | |
| /// m_MoveAction = m_PlayerInput.actions["move"]; | |
| /// } | |
| /// | |
| /// if (m_FireAction.triggered) | |
| /// /* firing logic... */; | |
| /// | |
| /// var move = m_MoveAction.ReadValue<Vector2>(); | |
| /// var look = m_LookAction.ReadValue<Vector2>(); | |
| /// /* Update transform from move&look... */ | |
| /// } | |
| /// } | |
| /// </code> | |
| /// </example> | |
| /// | |
| /// When enabled, PlayerInput will create an <see cref="InputUser"/> and pair devices to the | |
| /// user which are then specific to the player. The set of devices can be controlled explicitly | |
| /// when instantiating a PlayerInput through <see cref="Instantiate(GameObject,int,string,int,InputDevice[])"/> | |
| /// or <see cref="Instantiate(GameObject,int,string,int,InputDevice)"/>. This also makes it possible | |
| /// to assign the same device to two different players, e.g. for split-keyboard play. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// var p1 = PlayerInput.Instantiate(playerPrefab, | |
| /// controlScheme: "KeyboardLeft", device: Keyboard.current); | |
| /// var p2 = PlayerInput.Instantiate(playerPrefab, | |
| /// controlScheme: "KeyboardRight", device: Keyboard.current); | |
| /// </code> | |
| /// </example> | |
| /// | |
| /// If no specific devices are given to a PlayerInput, the component will look for compatible | |
| /// devices present in the system and pair them to itself automatically. If the PlayerInput's | |
| /// <see cref="actions"/> have control schemes defined for them, PlayerInput will look for a | |
| /// control scheme for which all required devices are available and not paired to any other player. | |
| /// It will try <see cref="defaultControlScheme"/> first (if set), but then fall back to trying | |
| /// all available schemes in order. Once a scheme is found for which all required devices are | |
| /// available, PlayerInput will pair those devices to itself and select the given scheme. | |
| /// | |
| /// If no control schemes are defined, PlayerInput will try to bind as many as-of-yet unpaired | |
| /// devices to itself as it can match to bindings present in the <see cref="actions"/>. This means | |
| /// that if, for example, there's binding for both keyboard and gamepad and there is one keyboard | |
| /// and two gamepads available when PlayerInput is enabled, all three devices will be paired to | |
| /// the player. | |
| /// | |
| /// Note that when using <see cref="PlayerInputManager"/>, device pairing to players is controlled | |
| /// from the joining logic. In that case, PlayerInput will automatically pair the device from which | |
| /// the player joined. If control schemes are present in <see cref="actions"/>, the first one compatible | |
| /// with that device is chosen. If additional devices are required, these will be paired from the pool | |
| /// of currently unpaired devices. | |
| /// | |
| /// Device pairings can be changed at any time by either manually controlling pairing through | |
| /// <see cref="InputUser.PerformPairingWithDevice"/> (and related methods) using a PlayerInput's | |
| /// assigned <see cref="user"/> or by switching control schemes (e.g. using | |
| /// <see cref="SwitchCurrentControlScheme(string,InputDevice[])"/>), if any are present in the PlayerInput's | |
| /// <see cref="actions"/>. | |
| /// | |
| /// When a player loses a device paired to it (e.g. when it is unplugged or loses power), <see cref="InputUser"/> | |
| /// will signal <see cref="InputUserChange.DeviceLost"/> which is also surfaced as a message, | |
| /// <see cref="deviceLostEvent"/>, or <see cref="onDeviceLost"/> (depending on <see cref="notificationBehavior"/>). | |
| /// When a device is reconnected, <see cref="InputUser"/> will signal <see cref="InputUserChange.DeviceRegained"/> | |
| /// which also is surfaced as a message, as <see cref="deviceRegainedEvent"/>, or <see cref="onDeviceRegained"/> | |
| /// (depending on <see cref="notificationBehavior"/>). | |
| /// | |
| /// When there is only a single active PlayerInput in the game, joining is not enabled (see | |
| /// <see cref="PlayerInputManager.joiningEnabled"/>), and <see cref="neverAutoSwitchControlSchemes"/> is not | |
| /// set to <c>true</c>, device pairings for the player will also update automatically based on device usage. | |
| /// | |
| /// If control schemes are present in <see cref="actions"/>, then if a device is used (not merely plugged in | |
| /// but rather receives input on a non-noisy, non-synthetic control) which is compatible with a control scheme | |
| /// other than the currently used one, PlayerInput will attempt to switch to that control scheme. Success depends | |
| /// on whether all device requirements for that scheme are met from the set of available devices. If a control | |
| /// scheme happens, <see cref="InputUser"/> signals <see cref="InputUserChange.ControlSchemeChanged"/> on | |
| /// <see cref="InputUser.onChange"/>. | |
| /// | |
| /// If no control schemes are present in <see cref="actions"/>, PlayerInput will automatically pair any newly | |
| /// available device to itself if the given device has any bindings available for it. | |
| /// | |
| /// Both behaviors described in the previous two paragraphs are automatically disabled if more than one | |
| /// PlayerInput is active. | |
| /// </remarks> | |
| /// <seealso cref="UnityEngine.InputSystem.PlayerInputManager"/> | |
| [] | |
| [] | |
| [] | |
| [] | |
| public class PlayerInput : MonoBehaviour | |
| { | |
| /// <summary> | |
| /// Name of the message that is sent with <c>UnityEngine.Object.SendMessage</c> when a | |
| /// player loses a device. | |
| /// </summary> | |
| /// <seealso cref="onDeviceLost"/> | |
| public const string DeviceLostMessage = "OnDeviceLost"; | |
| /// <summary> | |
| /// Name of the message that is sent with <c>UnityEngine.Object.SendMessage</c> when a | |
| /// player regains a device. | |
| /// </summary> | |
| /// <seealso cref="onDeviceRegained"/> | |
| public const string DeviceRegainedMessage = "OnDeviceRegained"; | |
| /// <summary> | |
| /// Name of the message that is sent with <c>UnityEngine.Object.SendMessage</c> when the | |
| /// controls used by a player are changed. | |
| /// </summary> | |
| /// <seealso cref="onControlsChanged"/> | |
| public const string ControlsChangedMessage = "OnControlsChanged"; | |
| /// <summary> | |
| /// Whether input is on the player is active. | |
| /// </summary> | |
| /// <value>If true, the player is receiving input.</value> | |
| /// <seealso cref="ActivateInput"/> | |
| /// <seealso cref="DeactivateInput"/> | |
| public bool inputIsActive => m_InputActive; | |
| [] | |
| public bool active => inputIsActive; | |
| /// <summary> | |
| /// Unique, zero-based index of the player. For example, <c>2</c> for the third player. | |
| /// </summary> | |
| /// <value>Unique index of the player.</value> | |
| /// <remarks> | |
| /// Once assigned, a player index will not change. | |
| /// | |
| /// Note that the player index does not necessarily correspond to the player's index in <see cref="all"/>. | |
| /// The array will always contain all currently enabled players so when a player is disabled or destroyed, | |
| /// it will be removed from the array. However, the player index of the remaining players will not change. | |
| /// </remarks> | |
| public int playerIndex => m_PlayerIndex; | |
| /// <summary> | |
| /// If split-screen is enabled (<see cref="UnityEngine.InputSystem.PlayerInputManager.splitScreen"/>), | |
| /// this is the index of the screen area used by the player. | |
| /// </summary> | |
| /// <value>Index of split-screen area assigned to player or -1 if the player is not | |
| /// using split-screen.</value> | |
| /// <remarks> | |
| /// Split screen areas are enumerated row by row and within rows, column by column. So, if, for example, | |
| /// there are four separate split-screen areas, the upper left one is #0, the upper right one is #1, | |
| /// the lower left one is #2, and the lower right one is #3. | |
| /// | |
| /// Split screen areas are usually assigned automatically but players can also be assigned to | |
| /// areas explicitly through <see cref="Instantiate(GameObject,int,string,int,InputDevice)"/> or | |
| /// <see cref="PlayerInputManager.JoinPlayer(int,int,string,InputDevice)"/>. | |
| /// </remarks> | |
| /// <seealso cref="camera"/> | |
| /// <seealso cref="PlayerInputManager.splitScreen"/> | |
| public int splitScreenIndex => m_SplitScreenIndex; | |
| /// <summary> | |
| /// Input actions associated with the player. | |
| /// </summary> | |
| /// <value>Asset holding the player's input actions.</value> | |
| /// <remarks> | |
| /// Note that every player will maintain a unique copy of the given actions such that | |
| /// each player receives an identical copy. When assigning the same actions to multiple players, | |
| /// the first player will use the given actions as is but any subsequent player will make a copy | |
| /// of the actions using <see cref="Object.Instantiate(Object)"/>. | |
| /// | |
| /// The asset may contain an arbitrary number of action maps. By setting <see cref="defaultActionMap"/>, | |
| /// one of them can be selected to enabled automatically when PlayerInput is enabled. If no default | |
| /// action map is selected, none of the action maps will be enabled by PlayerInput itself. Use | |
| /// <see cref="SwitchCurrentActionMap"/> or just call <see cref="InputActionMap.Enable"/> directly | |
| /// to enable a specific map. | |
| /// | |
| /// Notifications will be sent for all actions in the asset, not just for those in the first action | |
| /// map. This means that if additional maps are manually enabled and disabled, notifications will | |
| /// be sent for their actions as they receive input. | |
| /// </remarks> | |
| /// <seealso cref="InputUser.actions"/> | |
| /// <seealso cref="SwitchCurrentActionMap"/> | |
| public InputActionAsset actions | |
| { | |
| get | |
| { | |
| if (!m_ActionsInitialized && gameObject.activeInHierarchy) | |
| 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_ActionsInitialized) | |
| UninitializeActions(); | |
| } | |
| m_Actions = value; | |
| if (m_Enabled) | |
| { | |
| ClearCaches(); | |
| AssignUserAndDevices(); | |
| InitializeActions(); | |
| if (m_InputActive) | |
| ActivateInput(); | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// Name of the currently active control scheme. | |
| /// </summary> | |
| /// <value>Name of the currently active control scheme or <c>null</c>.</value> | |
| /// <remarks> | |
| /// Note that this property will be <c>null</c> if there are no control schemes | |
| /// defined in <see cref="actions"/>. | |
| /// </remarks> | |
| /// <seealso cref="SwitchCurrentControlScheme(UnityEngine.InputSystem.InputDevice[])"/> | |
| /// <seealso cref="defaultControlScheme"/> | |
| /// <seealso cref="InputActionAsset.controlSchemes"/> | |
| public string currentControlScheme | |
| { | |
| get | |
| { | |
| if (!m_InputUser.valid) | |
| return null; | |
| var scheme = m_InputUser.controlScheme; | |
| return scheme?.name; | |
| } | |
| } | |
| /// <summary> | |
| /// The default control scheme to try. | |
| /// </summary> | |
| /// <value>Name of the default control scheme.</value> | |
| /// <remarks> | |
| /// When PlayerInput is enabled and this is not <c>null</c> and not empty, the PlayerInput | |
| /// will look up the control scheme in <see cref="InputActionAsset.controlSchemes"/> of | |
| /// <see cref="actions"/>. If found, PlayerInput will try to activate the scheme. This will | |
| /// succeed only if all devices required by the control scheme are either already paired to | |
| /// the player or are available as devices not used by other PlayerInputs. | |
| /// | |
| /// Note that this property only determines the first control scheme to try. If using the | |
| /// control scheme fails, PlayerInput will fall back to trying the other control schemes | |
| /// (if any) available from <see cref="actions"/>. | |
| /// </remarks> | |
| /// <seealso cref="SwitchCurrentControlScheme(InputDevice[])"/> | |
| /// <seealso cref="currentControlScheme"/> | |
| public string defaultControlScheme | |
| { | |
| get => m_DefaultControlScheme; | |
| set => m_DefaultControlScheme = value; | |
| } | |
| /// <summary> | |
| /// If true, do not automatically switch control schemes even when there is only a single player. | |
| /// By default, this property is false. | |
| /// </summary> | |
| /// <value>If true, do not switch control schemes when other devices are used.</value> | |
| /// <remarks> | |
| /// By default, when there is only a single PlayerInput enabled, we assume that the game is in | |
| /// single-player mode and that the player should be able to freely switch between the control schemes | |
| /// supported by the game. For example, if the player is currently using mouse and keyboard, but is | |
| /// then switching to a gamepad, PlayerInput should automatically switch to the control scheme for | |
| /// gamepads, if present. | |
| /// | |
| /// When there is more than one PlayerInput or when joining is enabled <see cref="PlayerInputManager"/>, | |
| /// this behavior is automatically turned off as we wouldn't know which player is switching if a | |
| /// currently unpaired device is used. | |
| /// | |
| /// By setting this property to true, auto-switching of control schemes is forcibly turned off and | |
| /// will thus not be performed even if there is only a single PlayerInput in the game. | |
| /// | |
| /// Note that you can still switch control schemes manually using <see | |
| /// cref="SwitchCurrentControlScheme(string,InputDevice[])"/>. | |
| /// </remarks> | |
| /// <seealso cref="currentControlScheme"/> | |
| /// <seealso cref="isSinglePlayer"/> | |
| public bool neverAutoSwitchControlSchemes | |
| { | |
| get => m_NeverAutoSwitchControlSchemes; | |
| set | |
| { | |
| if (m_NeverAutoSwitchControlSchemes == value) | |
| return; | |
| m_NeverAutoSwitchControlSchemes = value; | |
| if (m_Enabled) | |
| { | |
| if (!value && !m_OnUnpairedDeviceUsedHooked) | |
| StartListeningForUnpairedDeviceActivity(); | |
| else if (value && m_OnUnpairedDeviceUsedHooked) | |
| StopListeningForUnpairedDeviceActivity(); | |
| } | |
| } | |
| } | |
| ////REVIEW: this is inconsistent; currentControlScheme is a string, this is an InputActionMap | |
| /// <summary> | |
| /// The currently enabled action map. | |
| /// </summary> | |
| /// <value>Reference to the currently enabled action or <c>null</c> if no action | |
| /// map has been enabled by PlayerInput.</value> | |
| /// <remarks> | |
| /// Note that the concept of "current action map" is local to PlayerInput. You can still freely | |
| /// enable and disable action maps directly on the <see cref="actions"/> asset. This property | |
| /// only tracks which action map has been enabled under the control of PlayerInput, i.e. either | |
| /// by means of <see cref="defaultActionMap"/> or by using <see cref="SwitchCurrentActionMap"/>. | |
| /// </remarks> | |
| /// <seealso cref="SwitchCurrentActionMap"/> | |
| public InputActionMap currentActionMap | |
| { | |
| get => m_CurrentActionMap; | |
| set | |
| { | |
| // If someone switches maps from an action callback, we may get here recursively | |
| // from Disable(). To avoid that, we null out the current action map while | |
| // we disable it. | |
| var oldMap = m_CurrentActionMap; | |
| m_CurrentActionMap = null; | |
| oldMap?.Disable(); | |
| // Switch to new map. | |
| m_CurrentActionMap = value; | |
| m_CurrentActionMap?.Enable(); | |
| } | |
| } | |
| /// <summary> | |
| /// Name (see <see cref="InputActionMap.name"/>) or ID (see <see cref="InputActionMap.id"/>) of the action | |
| /// map to enable by default. | |
| /// </summary> | |
| /// <value>Action map to enable by default or <c>null</c>.</value> | |
| /// <remarks> | |
| /// By default, when enabled, PlayerInput will not enable any of the actions in the <see cref="actions"/> | |
| /// asset. By setting this property, however, PlayerInput can be made to automatically enable the respective | |
| /// action map. | |
| /// </remarks> | |
| /// <seealso cref="currentActionMap"/> | |
| /// <seealso cref="SwitchCurrentActionMap"/> | |
| public string defaultActionMap | |
| { | |
| get => m_DefaultActionMap; | |
| set => m_DefaultActionMap = value; | |
| } | |
| /// <summary> | |
| /// Determines how the component notifies listeners about input actions and other input-related | |
| /// events pertaining to the player. | |
| /// </summary> | |
| /// <value>How to trigger notifications on events.</value> | |
| /// <remarks> | |
| /// By default, the component will use <see cref="GameObject.SendMessage(string,object)"/> to send messages | |
| /// to the <see cref="GameObject"/>. This can be changed by selecting a different <see cref="UnityEngine.InputSystem.PlayerNotifications"/> | |
| /// behavior. | |
| /// </remarks> | |
| /// <seealso cref="actionEvents"/> | |
| /// <seealso cref="deviceLostEvent"/> | |
| /// <seealso cref="deviceRegainedEvent"/> | |
| public PlayerNotifications notificationBehavior | |
| { | |
| get => m_NotificationBehavior; | |
| set | |
| { | |
| if (m_NotificationBehavior == value) | |
| return; | |
| if (m_Enabled) | |
| UninitializeActions(); | |
| m_NotificationBehavior = value; | |
| if (m_Enabled) | |
| InitializeActions(); | |
| } | |
| } | |
| /// <summary> | |
| /// List of events invoked in response to actions being triggered. | |
| /// </summary> | |
| /// <remarks> | |
| /// This array is only used if <see cref="notificationBehavior"/> is set to | |
| /// <see cref="UnityEngine.InputSystem.PlayerNotifications.InvokeUnityEvents"/>. | |
| /// </remarks> | |
| public ReadOnlyArray<ActionEvent> actionEvents | |
| { | |
| get => m_ActionEvents; | |
| set | |
| { | |
| if (m_Enabled) | |
| UninitializeActions(); | |
| m_ActionEvents = value.ToArray(); | |
| if (m_Enabled) | |
| InitializeActions(); | |
| } | |
| } | |
| /// <summary> | |
| /// Event that is triggered when the player loses a device (e.g. the batteries run out). | |
| /// </summary> | |
| /// <remarks> | |
| /// This event is only used if <see cref="notificationBehavior"/> is set to | |
| /// <see cref="UnityEngine.InputSystem.PlayerNotifications.InvokeUnityEvents"/>. | |
| /// </remarks> | |
| public DeviceLostEvent deviceLostEvent | |
| { | |
| get | |
| { | |
| if (m_DeviceLostEvent == null) | |
| m_DeviceLostEvent = new DeviceLostEvent(); | |
| return m_DeviceLostEvent; | |
| } | |
| } | |
| /// <summary> | |
| /// Event that is triggered when the player recovers from device loss and is good to go again. | |
| /// </summary> | |
| /// <remarks> | |
| /// This event is only used if <see cref="notificationBehavior"/> is set to | |
| /// <see cref="UnityEngine.InputSystem.PlayerNotifications.InvokeUnityEvents"/>. | |
| /// </remarks> | |
| public DeviceRegainedEvent deviceRegainedEvent | |
| { | |
| get | |
| { | |
| if (m_DeviceRegainedEvent == null) | |
| m_DeviceRegainedEvent = new DeviceRegainedEvent(); | |
| return m_DeviceRegainedEvent; | |
| } | |
| } | |
| /// <summary> | |
| /// Event that is triggered when the controls used by the player change. | |
| /// </summary> | |
| /// <remarks> | |
| /// This event is only used if <see cref="notificationBehavior"/> is set to | |
| /// <see cref="UnityEngine.InputSystem.PlayerNotifications.InvokeUnityEvents"/>. | |
| /// | |
| /// The event is trigger when the set of <see cref="devices"/> used by the player change, | |
| /// when the player switches to a different control scheme (see <see cref="currentControlScheme"/>), | |
| /// or when the bindings used by the player are changed (e.g. when rebinding them). Also, | |
| /// for <see cref="Keyboard"/> devices, the event is triggered when the currently used | |
| /// keyboard layout (see <see cref="Keyboard.keyboardLayout"/>) changes. | |
| /// </remarks> | |
| public ControlsChangedEvent controlsChangedEvent | |
| { | |
| get | |
| { | |
| if (m_ControlsChangedEvent == null) | |
| m_ControlsChangedEvent = new ControlsChangedEvent(); | |
| return m_ControlsChangedEvent; | |
| } | |
| } | |
| /// <summary> | |
| /// If <see cref="notificationBehavior"/> is set to <see cref="PlayerNotifications.InvokeCSharpEvents"/>, this | |
| /// event is triggered when an action fires. | |
| /// </summary> | |
| /// <value>Callbacks that get called when an action triggers.</value> | |
| /// <remarks> | |
| /// If <see cref="notificationBehavior"/> is not set to <see cref="PlayerNotifications.InvokeCSharpEvents"/>, the | |
| /// value of this property is ignored. | |
| /// | |
| /// The callbacks are called in sync (and with the same argument) with <see cref="InputAction.started"/>, | |
| /// <see cref="InputAction.performed"/>, and <see cref="InputAction.canceled"/>. | |
| /// </remarks> | |
| /// <seealso cref="InputActionMap.actionTriggered"/> | |
| /// <seealso cref="InputAction.started"/> | |
| /// <seealso cref="InputAction.performed"/> | |
| /// <seealso cref="InputAction.canceled"/> | |
| /// <seealso cref="actions"/> | |
| public event Action<InputAction.CallbackContext> onActionTriggered | |
| { | |
| add | |
| { | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| m_ActionTriggeredCallbacks.AddCallback(value); | |
| } | |
| remove | |
| { | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| m_ActionTriggeredCallbacks.RemoveCallback(value); | |
| } | |
| } | |
| /// <summary> | |
| /// If <see cref="notificationBehavior"/> is <see cref="PlayerNotifications.InvokeCSharpEvents"/>, this event | |
| /// is triggered when a device paired to the player is disconnected. | |
| /// </summary> | |
| /// <value>Callbacks that get called when the player loses a device.</value> | |
| /// <remarks> | |
| /// If <see cref="notificationBehavior"/> is not <see cref="PlayerNotifications.InvokeCSharpEvents"/>, the value | |
| /// of this property is ignored. | |
| /// | |
| /// The argument is the player that lost its device (i.e. the player on which the callback is installed). | |
| /// </remarks> | |
| /// <seealso cref="onDeviceRegained"/> | |
| /// <seealso cref="InputUserChange.DeviceLost"/> | |
| public event Action<PlayerInput> onDeviceLost | |
| { | |
| add | |
| { | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| m_DeviceLostCallbacks.AddCallback(value); | |
| } | |
| remove | |
| { | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| m_DeviceLostCallbacks.RemoveCallback(value); | |
| } | |
| } | |
| /// <summary> | |
| /// If <see cref="notificationBehavior"/> is <see cref="PlayerNotifications.InvokeCSharpEvents"/>, this event | |
| /// is triggered when the player previously lost a device and has now regained it or an equivalent device. | |
| /// </summary> | |
| /// <value>Callbacks that get called when the player regains a device.</value> | |
| /// <remarks> | |
| /// If <see cref="notificationBehavior"/> is not <see cref="PlayerNotifications.InvokeCSharpEvents"/>, the value | |
| /// of this property is ignored. | |
| /// | |
| /// The argument is the player that regained a device (i.e. the player on which the callback is installed). | |
| /// </remarks> | |
| /// <seealso cref="onDeviceLost"/> | |
| /// <seealso cref="InputUserChange.DeviceRegained"/> | |
| public event Action<PlayerInput> onDeviceRegained | |
| { | |
| add | |
| { | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| m_DeviceRegainedCallbacks.AddCallback(value); | |
| } | |
| remove | |
| { | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| m_DeviceRegainedCallbacks.RemoveCallback(value); | |
| } | |
| } | |
| /// <summary> | |
| /// If <see cref="notificationBehavior"/> is <see cref="PlayerNotifications.InvokeCSharpEvents"/>, this event | |
| /// is triggered when the controls used by the players are changed. | |
| /// </summary> | |
| /// <remarks> | |
| /// The callback is invoked when the set of <see cref="devices"/> used by the player change, | |
| /// when the player switches to a different control scheme (see <see cref="currentControlScheme"/>), | |
| /// or when the bindings used by the player are changed (e.g. when rebinding them). Also, | |
| /// for <see cref="Keyboard"/> devices, the callback is invoked when the currently used | |
| /// keyboard layout (see <see cref="Keyboard.keyboardLayout"/>) changes. | |
| /// </remarks> | |
| public event Action<PlayerInput> onControlsChanged | |
| { | |
| add | |
| { | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| m_ControlsChangedCallbacks.AddCallback(value); | |
| } | |
| remove | |
| { | |
| if (value == null) | |
| throw new ArgumentNullException(nameof(value)); | |
| m_ControlsChangedCallbacks.RemoveCallback(value); | |
| } | |
| } | |
| ////TODO: clarify the relationship to raycasting in the UI input module | |
| /// <summary> | |
| /// Optional camera associated with the player. | |
| /// </summary> | |
| /// <value>Camera specific to the player or <c>null</c>.</value> | |
| /// <remarks> | |
| /// This is <c>null</c> by default. | |
| /// | |
| /// Associating a camera with a player is necessary only when using split-screen (see <see cref="PlayerInputManager.splitScreen"/>). | |
| /// </remarks> | |
| public | |
| // camera property is deprecated and only available in Editor. | |
| new | |
| Camera camera | |
| { | |
| get => m_Camera; | |
| set => m_Camera = value; | |
| } | |
| /// <summary> | |
| /// UI InputModule that should have it's input actions synchronized to this PlayerInput's actions. | |
| /// </summary> | |
| public InputSystemUIInputModule uiInputModule | |
| { | |
| get => m_UIInputModule; | |
| set | |
| { | |
| if (m_UIInputModule == value) | |
| return; | |
| if (m_UIInputModule != null && m_UIInputModule.actionsAsset == m_Actions) | |
| m_UIInputModule.actionsAsset = null; | |
| m_UIInputModule = value; | |
| if (m_UIInputModule != null && m_Actions != null) | |
| m_UIInputModule.actionsAsset = m_Actions; | |
| } | |
| } | |
| /// <summary> | |
| /// The internal user tied to the player. | |
| /// </summary> | |
| public InputUser user => m_InputUser; | |
| /// <summary> | |
| /// The devices paired to the player. | |
| /// </summary> | |
| /// <value>List of devices paired to player.</value> | |
| /// <remarks> | |
| /// </remarks> | |
| /// <seealso cref="InputUser.pairedDevices"/> | |
| public ReadOnlyArray<InputDevice> devices | |
| { | |
| get | |
| { | |
| if (!m_InputUser.valid) | |
| return new ReadOnlyArray<InputDevice>(); | |
| return m_InputUser.pairedDevices; | |
| } | |
| } | |
| /// <summary> | |
| /// Whether the player is missed required devices. This means that the player's | |
| /// input setup is probably at least partially non-functional. | |
| /// </summary> | |
| /// <value>True if the player is missing devices required by the control scheme.</value> | |
| /// <remarks> | |
| /// This can happen, for example, if the a device is unplugged during the game. | |
| /// </remarks> | |
| /// <seealso cref="InputControlScheme.deviceRequirements"/> | |
| /// <seealso cref="InputUser.hasMissingRequiredDevices"/> | |
| public bool hasMissingRequiredDevices => user.valid && user.hasMissingRequiredDevices; | |
| /// <summary> | |
| /// List of all players that are currently joined. Sorted by <see cref="playerIndex"/> in | |
| /// increasing order. | |
| /// </summary> | |
| /// <value>List of active PlayerInputs.</value> | |
| /// <remarks> | |
| /// While the list is sorted by <see cref="playerIndex"/>, note that this does not mean that the <see cref="playerIndex"/> | |
| /// of a player corresponds to the index in this list. If, for example, three players join and then the second player leaves, | |
| /// the list will contain one player with <see cref="playerIndex"/> 0 followed by one player with <see cref="playerIndex"/> 2. | |
| /// </remarks> | |
| /// <seealso cref="PlayerInputManager.JoinPlayer(int,int,string,InputDevice)"/> | |
| /// <seealso cref="Instantiate(GameObject,int,string,int,InputDevice)"/> | |
| public static ReadOnlyArray<PlayerInput> all => new ReadOnlyArray<PlayerInput>(s_AllActivePlayers, 0, s_AllActivePlayersCount); | |
| /// <summary> | |
| /// Whether PlayerInput operates in single-player mode. | |
| /// </summary> | |
| /// <value>If true, there is at most a single PlayerInput.</value> | |
| /// <remarks> | |
| /// Single-player mode is active while there is at most one PlayerInput (there can also be none) and | |
| /// while joining is not enabled in <see cref="PlayerInputManager"/> (if one exists). See <see cref="PlayerInputManager.joiningEnabled"/>. | |
| /// | |
| /// Automatic control scheme switching (if enabled) is predicated on single-player mode being active. | |
| /// </remarks> | |
| /// <seealso cref="neverAutoSwitchControlSchemes"/> | |
| public static bool isSinglePlayer => | |
| s_AllActivePlayersCount <= 1 && | |
| (PlayerInputManager.instance == null || !PlayerInputManager.instance.joiningEnabled); | |
| /// <summary> | |
| /// Return the first device of the given type from <see cref="devices"/> paired to the player. | |
| /// If no device of this type is paired to the player, return <c>null</c>. | |
| /// </summary> | |
| /// <typeparam name="TDevice">Type of device to look for (such as <see cref="Mouse"/>). Can be a supertype | |
| /// of the actual device type. For example, querying for <see cref="Pointer"/>, may return a <see cref="Mouse"/>.</typeparam> | |
| /// <returns>The first device paired to the player that is of the given type or <c>null</c> if the player | |
| /// does not have a matching device.</returns> | |
| /// <seealso cref="devices"/> | |
| public TDevice GetDevice<TDevice>() | |
| where TDevice : InputDevice | |
| { | |
| foreach (var device in devices) | |
| if (device is TDevice deviceOfType) | |
| return deviceOfType; | |
| return null; | |
| } | |
| /// <summary> | |
| /// Enable input on the player. | |
| /// </summary> | |
| /// <remarks> | |
| /// Input will automatically be activated when the PlayerInput component is enabled. However, this method | |
| /// can be called to reactivate input after deactivating it with <see cref="DeactivateInput"/>. | |
| /// | |
| /// Note that activating input will activate the current action map only (see <see cref="currentActionMap"/>). | |
| /// </remarks> | |
| /// <see cref="inputIsActive"/> | |
| /// <seealso cref="DeactivateInput"/> | |
| 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(); | |
| } | |
| /// <summary> | |
| /// Disable input on the player. | |
| /// </summary> | |
| /// <remarks> | |
| /// Input is automatically activated when the PlayerInput component is enabled. This method can be | |
| /// used to deactivate input manually. | |
| /// | |
| /// Note that activating input will deactivate the current action map only (see <see cref="currentActionMap"/>). | |
| /// </remarks> | |
| /// <see cref="ActivateInput"/> | |
| /// <see cref="inputIsActive"/> | |
| public void DeactivateInput() | |
| { | |
| m_CurrentActionMap?.Disable(); | |
| m_InputActive = false; | |
| } | |
| [] | |
| public void PassivateInput() | |
| { | |
| DeactivateInput(); | |
| } | |
| /// <summary> | |
| /// Switch the current control scheme to one that fits the given set of devices. | |
| /// </summary> | |
| /// <param name="devices">A list of input devices. Note that if any of the devices is already paired to another | |
| /// player, the device will end up paired to both players.</param> | |
| /// <returns>True if the switch was successful, false otherwise. The latter can happen, for example, if | |
| /// <see cref="actions"/> does not have a control scheme that fits the given set of devices.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="devices"/> is <c>null</c>.</exception> | |
| /// <exception cref="InvalidOperationException"><see cref="actions"/> has not been assigned.</exception> | |
| /// <remarks> | |
| /// The player's currently paired devices (see <see cref="devices"/>) will get unpaired. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// // Switch the first player to keyboard and mouse. | |
| /// PlayerInput.all[0] | |
| /// .SwitchCurrentControlScheme(Keyboard.current, Mouse.current); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| /// <seealso cref="currentControlScheme"/> | |
| /// <seealso cref="InputActionAsset.controlSchemes"/> | |
| public bool SwitchCurrentControlScheme(params InputDevice[] devices) | |
| { | |
| if (devices == null) | |
| throw new ArgumentNullException(nameof(devices)); | |
| if (actions == null) | |
| throw new InvalidOperationException( | |
| "Must set actions on PlayerInput in order to be able to switch control schemes"); | |
| // Find control scheme matching given devices in associated action asset | |
| var scheme = InputControlScheme.FindControlSchemeForDevices(devices, actions.controlSchemes); | |
| if (!scheme.HasValue) | |
| return false; | |
| var controlScheme = scheme.Value; | |
| SwitchControlSchemeInternal(ref controlScheme, devices); | |
| return true; | |
| } | |
| ////REVIEW: these should just be SwitchControlScheme | |
| /// <summary> | |
| /// Switch the player to use the given control scheme together with the given devices. | |
| /// </summary> | |
| /// <param name="controlScheme">Name of the control scheme. See <see cref="InputControlScheme.name"/>.</param> | |
| /// <param name="devices">A list of devices.</param> | |
| /// <exception cref="ArgumentNullException"><paramref name="devices"/> is <c>null</c> -or- <paramref name="controlScheme"/> is | |
| /// <c>null</c> or empty.</exception> | |
| /// <remarks> | |
| /// This method can be used to explicitly force a combination of control scheme and a specific set of | |
| /// devices. | |
| /// | |
| /// <example> | |
| /// <code> | |
| /// // Put player 1 on the "Gamepad" control scheme together | |
| /// // with the second gamepad. | |
| /// PlayerInput.all[0].SwitchControlScheme( | |
| /// "Gamepad", | |
| /// Gamepad.all[1]); | |
| /// </code> | |
| /// </example> | |
| /// | |
| /// The player's currently paired devices (see <see cref="devices"/>) will get unpaired. | |
| /// </remarks> | |
| /// <seealso cref="InputActionAsset.controlSchemes"/> | |
| /// <seealso cref="currentControlScheme"/> | |
| public void SwitchCurrentControlScheme(string controlScheme, params InputDevice[] devices) | |
| { | |
| if (string.IsNullOrEmpty(controlScheme)) | |
| throw new ArgumentNullException(nameof(controlScheme)); | |
| if (devices == null) | |
| throw new ArgumentNullException(nameof(devices)); | |
| user.FindControlScheme(controlScheme, out InputControlScheme scheme); // throws if not found | |
| SwitchControlSchemeInternal(ref scheme, devices); | |
| } | |
| 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; | |
| } | |
| /// <summary> | |
| /// Return the Nth player. | |
| /// </summary> | |
| /// <param name="playerIndex">Index of the player to return.</param> | |
| /// <returns>The player with the given player index or <c>null</c> if no such | |
| /// player exists.</returns> | |
| /// <seealso cref="PlayerInput.playerIndex"/> | |
| public static PlayerInput GetPlayerByIndex(int playerIndex) | |
| { | |
| for (var i = 0; i < s_AllActivePlayersCount; ++i) | |
| if (s_AllActivePlayers[i].playerIndex == playerIndex) | |
| return s_AllActivePlayers[i]; | |
| return null; | |
| } | |
| /// <summary> | |
| /// Find the first PlayerInput who the given device is paired to. | |
| /// </summary> | |
| /// <param name="device">An input device.</param> | |
| /// <returns>The player who is paired to the given device or <c>null</c> if no | |
| /// PlayerInput currently is paired to <paramref name="device"/>.</returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="device"/> is <c>null</c>.</exception> | |
| /// <remarks> | |
| /// <example> | |
| /// <code> | |
| /// // Find the player paired to first gamepad. | |
| /// var player = PlayerInput.FindFirstPairedToDevice(Gamepad.all[0]); | |
| /// </code> | |
| /// </example> | |
| /// </remarks> | |
| public static PlayerInput FindFirstPairedToDevice(InputDevice device) | |
| { | |
| if (device == null) | |
| throw new ArgumentNullException(nameof(device)); | |
| for (var i = 0; i < s_AllActivePlayersCount; ++i) | |
| { | |
| if (ReadOnlyArrayExtensions.ContainsReference(s_AllActivePlayers[i].devices, device)) | |
| return s_AllActivePlayers[i]; | |
| } | |
| return null; | |
| } | |
| /// <summary> | |
| /// Instantiate a player object and set up and enable its inputs. | |
| /// </summary> | |
| /// <param name="prefab">Prefab to clone. Must contain a PlayerInput component somewhere in its hierarchy.</param> | |
| /// <param name="playerIndex">Player index to assign to the player. See <see cref="PlayerInput.playerIndex"/>. | |
| /// By default will be assigned automatically based on how many players are in <see cref="all"/>.</param> | |
| /// <param name="controlScheme">Control scheme to activate</param> | |
| /// <param name="splitScreenIndex"></param> | |
| /// <param name="pairWithDevice">Device to pair to the user. By default, this is <c>null</c> which means | |
| /// that PlayerInput will automatically pair with available, unpaired devices based on the control schemes (if any) | |
| /// present in <see cref="actions"/> or on the bindings therein (if no control schemes are present).</param> | |
| /// <returns></returns> | |
| /// <exception cref="ArgumentNullException"><paramref name="prefab"/> is <c>null</c>.</exception> | |
| public static PlayerInput Instantiate(GameObject prefab, int playerIndex = -1, string controlScheme = null, | |
| int splitScreenIndex = -1, InputDevice pairWithDevice = null) | |
| { | |
| if (prefab == null) | |
| throw new ArgumentNullException(nameof(prefab)); | |
| // Set initialization data. | |
| s_InitPlayerIndex = playerIndex; | |
| s_InitSplitScreenIndex = splitScreenIndex; | |
| s_InitControlScheme = controlScheme; | |
| if (pairWithDevice != null) | |
| ArrayHelpers.AppendWithCapacity(ref s_InitPairWithDevices, ref s_InitPairWithDevicesCount, pairWithDevice); | |
| return DoInstantiate(prefab); | |
| } | |
| ////TODO: allow instantiating with an existing InputUser | |
| /// <summary> | |
| /// A wrapper around <see cref="Object.Instantiate(Object)"/> that allows instantiating a player prefab and | |
| /// automatically pair one or more specific devices to the newly created player. | |
| /// </summary> | |
| /// <param name="prefab">A player prefab containing a <see cref="PlayerInput"/> component in its hierarchy.</param> | |
| /// <param name="playerIndex"></param> | |
| /// <param name="controlScheme"></param> | |
| /// <param name="splitScreenIndex"></param> | |
| /// <param name="pairWithDevices"></param> | |
| /// <returns></returns> | |
| /// <remarks> | |
| /// Note that unlike <see cref="Object.Instantiate(Object)"/>, this method will always activate the resulting | |
| /// <see cref="GameObject"/> and its components. | |
| /// </remarks> | |
| public static PlayerInput Instantiate(GameObject prefab, int playerIndex = -1, string controlScheme = null, | |
| int splitScreenIndex = -1, params InputDevice[] pairWithDevices) | |
| { | |
| if (prefab == null) | |
| throw new ArgumentNullException(nameof(prefab)); | |
| // Set initialization data. | |
| s_InitPlayerIndex = playerIndex; | |
| s_InitSplitScreenIndex = splitScreenIndex; | |
| s_InitControlScheme = controlScheme; | |
| if (pairWithDevices != null) | |
| { | |
| for (var i = 0; i < pairWithDevices.Length; ++i) | |
| ArrayHelpers.AppendWithCapacity(ref s_InitPairWithDevices, ref s_InitPairWithDevicesCount, pairWithDevices[i]); | |
| } | |
| return DoInstantiate(prefab); | |
| } | |
| private static PlayerInput DoInstantiate(GameObject prefab) | |
| { | |
| var destroyIfDeviceSetupUnsuccessful = s_DestroyIfDeviceSetupUnsuccessful; | |
| GameObject instance; | |
| try | |
| { | |
| instance = Object.Instantiate(prefab); | |
| instance.SetActive(true); | |
| } | |
| finally | |
| { | |
| // Reset init data. | |
| s_InitPairWithDevicesCount = 0; | |
| if (s_InitPairWithDevices != null) | |
| Array.Clear(s_InitPairWithDevices, 0, s_InitPairWithDevicesCount); | |
| s_InitControlScheme = null; | |
| s_InitPlayerIndex = -1; | |
| s_InitSplitScreenIndex = -1; | |
| s_DestroyIfDeviceSetupUnsuccessful = false; | |
| } | |
| var playerInput = instance.GetComponentInChildren<PlayerInput>(); | |
| if (playerInput == null) | |
| { | |
| DestroyImmediate(instance); | |
| Debug.LogError("The GameObject does not have a PlayerInput component", prefab); | |
| return null; | |
| } | |
| if (destroyIfDeviceSetupUnsuccessful && (!playerInput.user.valid || playerInput.hasMissingRequiredDevices)) | |
| { | |
| DestroyImmediate(instance); | |
| return null; | |
| } | |
| return playerInput; | |
| } | |
| [] | |
| [] internal InputActionAsset m_Actions; | |
| [] | |
| [] internal PlayerNotifications m_NotificationBehavior; | |
| [] | |
| [] internal InputSystemUIInputModule m_UIInputModule; | |
| [] | |
| [] internal DeviceLostEvent m_DeviceLostEvent; | |
| [] internal DeviceRegainedEvent m_DeviceRegainedEvent; | |
| [] internal ControlsChangedEvent m_ControlsChangedEvent; | |
| [] internal ActionEvent[] m_ActionEvents; | |
| [] internal bool m_NeverAutoSwitchControlSchemes; | |
| [] internal string m_DefaultControlScheme;////REVIEW: should we have IDs for these so we can rename safely? | |
| [] internal string m_DefaultActionMap; | |
| [] internal int m_SplitScreenIndex = -1; | |
| [ | |
| ] | |
| [] internal Camera m_Camera; | |
| // Value object we use when sending messages via SendMessage() or BroadcastMessage(). Can be ignored | |
| // by the receiver. We reuse the same object over and over to avoid allocating garbage. | |
| [] private InputValue m_InputValueObject; | |
| [] internal InputActionMap m_CurrentActionMap; | |
| [] private int m_PlayerIndex = -1; | |
| [] private bool m_InputActive; | |
| [] private bool m_Enabled; | |
| [] internal bool m_ActionsInitialized; | |
| [] private Dictionary<string, string> m_ActionMessageNames; | |
| [] private InputUser m_InputUser; | |
| [] private Action<InputAction.CallbackContext> m_ActionTriggeredDelegate; | |
| [] private CallbackArray<Action<PlayerInput>> m_DeviceLostCallbacks; | |
| [] private CallbackArray<Action<PlayerInput>> m_DeviceRegainedCallbacks; | |
| [] private CallbackArray<Action<PlayerInput>> m_ControlsChangedCallbacks; | |
| [] private CallbackArray<Action<InputAction.CallbackContext>> m_ActionTriggeredCallbacks; | |
| [] private Action<InputControl, InputEventPtr> m_UnpairedDeviceUsedDelegate; | |
| [] private Func<InputDevice, InputEventPtr, bool> m_PreFilterUnpairedDeviceUsedDelegate; | |
| [] private bool m_OnUnpairedDeviceUsedHooked; | |
| [] private Action<InputDevice, InputDeviceChange> m_DeviceChangeDelegate; | |
| [] private bool m_OnDeviceChangeHooked; | |
| internal static int s_AllActivePlayersCount; | |
| internal static PlayerInput[] s_AllActivePlayers; | |
| private static Action<InputUser, InputUserChange, InputDevice> s_UserChangeDelegate; | |
| // The following information is used when the next PlayerInput component is enabled. | |
| private static int s_InitPairWithDevicesCount; | |
| private static InputDevice[] s_InitPairWithDevices; | |
| private static int s_InitPlayerIndex = -1; | |
| private static int s_InitSplitScreenIndex = -1; | |
| private static string s_InitControlScheme; | |
| internal static bool s_DestroyIfDeviceSetupUnsuccessful; | |
| private void InitializeActions() | |
| { | |
| if (m_ActionsInitialized) | |
| return; | |
| if (m_Actions == null) | |
| return; | |
| // Check if we need to duplicate our actions by looking at all other players. If any | |
| // has the same actions, duplicate. | |
| for (var i = 0; i < s_AllActivePlayersCount; ++i) | |
| if (s_AllActivePlayers[i].m_Actions == m_Actions && s_AllActivePlayers[i] != this) | |
| { | |
| 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]); | |
| } | |
| break; | |
| } | |
| if (uiInputModule != null) | |
| uiInputModule.actionsAsset = m_Actions; | |
| switch (m_NotificationBehavior) | |
| { | |
| case PlayerNotifications.SendMessages: | |
| case PlayerNotifications.BroadcastMessages: | |
| InstallOnActionTriggeredHook(); | |
| if (m_ActionMessageNames == null) | |
| CacheMessageNames(); | |
| break; | |
| case PlayerNotifications.InvokeCSharpEvents: | |
| InstallOnActionTriggeredHook(); | |
| break; | |
| case PlayerNotifications.InvokeUnityEvents: | |
| { | |
| // 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) | |
| continue; | |
| action.performed += actionEvent.Invoke; | |
| action.canceled += actionEvent.Invoke; | |
| action.started += actionEvent.Invoke; | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| m_ActionsInitialized = true; | |
| } | |
| private void UninitializeActions() | |
| { | |
| if (!m_ActionsInitialized) | |
| return; | |
| if (m_Actions == null) | |
| return; | |
| UninstallOnActionTriggeredHook(); | |
| if (m_NotificationBehavior == PlayerNotifications.InvokeUnityEvents && 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; | |
| } | |
| private void InstallOnActionTriggeredHook() | |
| { | |
| if (m_ActionTriggeredDelegate == null) | |
| m_ActionTriggeredDelegate = OnActionTriggered; | |
| foreach (var actionMap in m_Actions.actionMaps) | |
| actionMap.actionTriggered += m_ActionTriggeredDelegate; | |
| } | |
| private void UninstallOnActionTriggeredHook() | |
| { | |
| if (m_ActionTriggeredDelegate != null) | |
| foreach (var actionMap in m_Actions.actionMaps) | |
| actionMap.actionTriggered -= m_ActionTriggeredDelegate; | |
| } | |
| private void OnActionTriggered(InputAction.CallbackContext context) | |
| { | |
| if (!m_InputActive) | |
| return; | |
| // We shouldn't go through this method when using UnityEvents. With events, | |
| // the callbacks should be wired up directly rather than going all to this method. | |
| Debug.Assert(m_NotificationBehavior != PlayerNotifications.InvokeUnityEvents, | |
| "OnActionTriggered callback should not be installed if notification behavior is set to InvokeUnityEvents"); | |
| switch (m_NotificationBehavior) | |
| { | |
| case PlayerNotifications.InvokeCSharpEvents: | |
| DelegateHelpers.InvokeCallbacksSafe(ref m_ActionTriggeredCallbacks, context, "PlayerInput.onActionTriggered"); | |
| break; | |
| case PlayerNotifications.BroadcastMessages: | |
| case PlayerNotifications.SendMessages: | |
| // ATM we only care about `performed` and, in the case of value actions, `canceled`. | |
| var action = context.action; | |
| if (!(context.performed || (context.canceled && action.type == InputActionType.Value))) | |
| return; | |
| // Find message name for action. | |
| if (m_ActionMessageNames == null) | |
| CacheMessageNames(); | |
| var messageName = m_ActionMessageNames[action.m_Id]; | |
| // Cache value. | |
| if (m_InputValueObject == null) | |
| m_InputValueObject = new InputValue(); | |
| m_InputValueObject.m_Context = context; | |
| // Send message. | |
| if (m_NotificationBehavior == PlayerNotifications.BroadcastMessages) | |
| BroadcastMessage(messageName, m_InputValueObject, SendMessageOptions.DontRequireReceiver); | |
| else | |
| SendMessage(messageName, m_InputValueObject, SendMessageOptions.DontRequireReceiver); | |
| // Reset context so calling Get() will result in an exception. | |
| m_InputValueObject.m_Context = null; | |
| break; | |
| } | |
| } | |
| private void CacheMessageNames() | |
| { | |
| if (m_Actions == null) | |
| return; | |
| if (m_ActionMessageNames != null) | |
| m_ActionMessageNames.Clear(); | |
| else | |
| m_ActionMessageNames = new Dictionary<string, string>(); | |
| foreach (var action in m_Actions) | |
| { | |
| action.MakeSureIdIsInPlace(); | |
| var name = CSharpCodeHelpers.MakeTypeName(action.name); | |
| m_ActionMessageNames[action.m_Id] = "On" + name; | |
| } | |
| } | |
| private void ClearCaches() | |
| { | |
| } | |
| /// <summary> | |
| /// Initialize <see cref="user"/> and <see cref="devices"/>. | |
| /// </summary> | |
| 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) | |
| { | |
| // If we have devices we are meant to pair with, do so. Otherwise, don't | |
| // do anything as we don't know what kind of input to look for. | |
| if (s_InitPairWithDevicesCount > 0) | |
| { | |
| for (var i = 0; i < s_InitPairWithDevicesCount; ++i) | |
| m_InputUser = InputUser.PerformPairingWithDevice(s_InitPairWithDevices[i], m_InputUser); | |
| } | |
| else | |
| { | |
| // Make sure user is invalid. | |
| m_InputUser = new InputUser(); | |
| } | |
| return; | |
| } | |
| // If we have control schemes, try to find the one we should use. | |
| if (m_Actions.controlSchemes.Count > 0) | |
| { | |
| if (!string.IsNullOrEmpty(s_InitControlScheme)) | |
| { | |
| // We've been given a control scheme to initialize this. Try that one and | |
| // that one only. Might mean we end up with missing devices. | |
| var controlScheme = m_Actions.FindControlScheme(s_InitControlScheme); | |
| if (controlScheme == null) | |
| { | |
| Debug.LogError($"No control scheme '{s_InitControlScheme}' in '{m_Actions}'", this); | |
| } | |
| else | |
| { | |
| TryToActivateControlScheme(controlScheme.Value); | |
| } | |
| } | |
| else if (!string.IsNullOrEmpty(m_DefaultControlScheme)) | |
| { | |
| // There's a control scheme we should try by default. | |
| var controlScheme = m_Actions.FindControlScheme(m_DefaultControlScheme); | |
| if (controlScheme == null) | |
| { | |
| Debug.LogError($"Cannot find default control scheme '{m_DefaultControlScheme}' in '{m_Actions}'", this); | |
| } | |
| else | |
| { | |
| TryToActivateControlScheme(controlScheme.Value); | |
| } | |
| } | |
| // If we did not end up with a usable scheme by now but we've been given devices to pair with, | |
| // search for a control scheme matching the given devices. | |
| if (s_InitPairWithDevicesCount > 0 && (!m_InputUser.valid || m_InputUser.controlScheme == null)) | |
| { | |
| // The devices we've been given may not be all the devices required to satisfy a given control scheme so we | |
| // want to pick any one control scheme that is the best match for the devices we have regardless of whether | |
| // we'll need additional devices. TryToActivateControlScheme will take care of that. | |
| var controlScheme = InputControlScheme.FindControlSchemeForDevices( | |
| new ReadOnlyArray<InputDevice>(s_InitPairWithDevices, 0, s_InitPairWithDevicesCount), m_Actions.controlSchemes, | |
| allowUnsuccesfulMatch: true); | |
| if (controlScheme != null) | |
| TryToActivateControlScheme(controlScheme.Value); | |
| } | |
| // If we don't have a working control scheme by now and we haven't been instructed to use | |
| // one specific control scheme, try each one in the asset one after the other until we | |
| // either find one we can use or run out of options. | |
| else if ((!m_InputUser.valid || m_InputUser.controlScheme == null) && string.IsNullOrEmpty(s_InitControlScheme)) | |
| { | |
| using (var availableDevices = InputUser.GetUnpairedInputDevices()) | |
| { | |
| var controlScheme = InputControlScheme.FindControlSchemeForDevices(availableDevices, m_Actions.controlSchemes); | |
| if (controlScheme != null) | |
| TryToActivateControlScheme(controlScheme.Value); | |
| } | |
| } | |
| } | |
| else | |
| { | |
| // There's no control schemes in the asset. If we've been given a set of devices, | |
| // we run with those (regardless of whether there's bindings for them in the actions or not). | |
| // If we haven't been given any devices, we go through all bindings in the asset and whatever | |
| // device is present that matches the binding and that isn't used by any other player, we'll | |
| // pair to the player. | |
| if (s_InitPairWithDevicesCount > 0) | |
| { | |
| for (var i = 0; i < s_InitPairWithDevicesCount; ++i) | |
| m_InputUser = InputUser.PerformPairingWithDevice(s_InitPairWithDevices[i], m_InputUser); | |
| } | |
| else | |
| { | |
| // Pair all devices for which we have a binding. | |
| using (var availableDevices = InputUser.GetUnpairedInputDevices()) | |
| { | |
| for (var i = 0; i < availableDevices.Count; ++i) | |
| { | |
| var device = availableDevices[i]; | |
| if (!HaveBindingForDevice(device)) | |
| continue; | |
| m_InputUser = InputUser.PerformPairingWithDevice(device, m_InputUser); | |
| } | |
| } | |
| } | |
| } | |
| // 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(m_Actions); | |
| } | |
| private bool HaveBindingForDevice(InputDevice device) | |
| { | |
| if (m_Actions == null) | |
| return false; | |
| var actionMaps = m_Actions.actionMaps; | |
| for (var i = 0; i < actionMaps.Count; ++i) | |
| { | |
| var actionMap = actionMaps[i]; | |
| if (actionMap.IsUsableWithDevice(device)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| private void UnassignUserAndDevices() | |
| { | |
| if (m_InputUser.valid) | |
| m_InputUser.UnpairDevicesAndRemoveUser(); | |
| if (m_Actions != null) | |
| m_Actions.devices = null; | |
| } | |
| private bool TryToActivateControlScheme(InputControlScheme controlScheme) | |
| { | |
| ////FIXME: this will fall apart if account management is involved and a user needs to log in on device first | |
| // Pair any devices we may have been given. | |
| if (s_InitPairWithDevicesCount > 0) | |
| { | |
| ////REVIEW: should AndPairRemainingDevices() require that there is at least one existing | |
| //// device paired to the user that is usable with the given control scheme? | |
| // First make sure that all of the devices actually work with the given control scheme. | |
| // We're fine having to pair additional devices but we don't want the situation where | |
| // we have the player grab all the devices in s_InitPairWithDevices along with a control | |
| // scheme that fits none of them and then AndPairRemainingDevices() supplying the devices | |
| // actually needed by the control scheme. | |
| for (var i = 0; i < s_InitPairWithDevicesCount; ++i) | |
| { | |
| var device = s_InitPairWithDevices[i]; | |
| if (!controlScheme.SupportsDevice(device)) | |
| return false; | |
| } | |
| // We're good. Give the devices to the user. | |
| for (var i = 0; i < s_InitPairWithDevicesCount; ++i) | |
| { | |
| var device = s_InitPairWithDevices[i]; | |
| m_InputUser = InputUser.PerformPairingWithDevice(device, m_InputUser); | |
| } | |
| } | |
| if (!m_InputUser.valid) | |
| m_InputUser = InputUser.CreateUserWithoutPairedDevices(); | |
| m_InputUser.ActivateControlScheme(controlScheme).AndPairRemainingDevices(); | |
| if (user.hasMissingRequiredDevices) | |
| { | |
| m_InputUser.ActivateControlScheme(null); | |
| m_InputUser.UnpairDevices(); | |
| return false; | |
| } | |
| return true; | |
| } | |
| private void AssignPlayerIndex() | |
| { | |
| if (s_InitPlayerIndex != -1) | |
| m_PlayerIndex = s_InitPlayerIndex; | |
| else | |
| { | |
| var minPlayerIndex = int.MaxValue; | |
| var maxPlayerIndex = int.MinValue; | |
| for (var i = 0; i < s_AllActivePlayersCount; ++i) | |
| { | |
| var playerIndex = s_AllActivePlayers[i].playerIndex; | |
| minPlayerIndex = Math.Min(minPlayerIndex, playerIndex); | |
| maxPlayerIndex = Math.Max(maxPlayerIndex, playerIndex); | |
| } | |
| if (minPlayerIndex != int.MaxValue && minPlayerIndex > 0) | |
| { | |
| // There's an index between 0 and the current minimum available. | |
| m_PlayerIndex = minPlayerIndex - 1; | |
| } | |
| else if (maxPlayerIndex != int.MinValue) | |
| { | |
| // There may be an index between the minimum and maximum available. | |
| // Search the range. If there's nothing, create a new maximum. | |
| for (var i = minPlayerIndex; i < maxPlayerIndex; ++i) | |
| { | |
| if (GetPlayerByIndex(i) == null) | |
| { | |
| m_PlayerIndex = i; | |
| return; | |
| } | |
| } | |
| m_PlayerIndex = maxPlayerIndex + 1; | |
| } | |
| else | |
| m_PlayerIndex = 0; | |
| } | |
| } | |
| private void OnEnable() | |
| { | |
| m_Enabled = true; | |
| using (InputActionRebindingExtensions.DeferBindingResolution()) | |
| { | |
| AssignPlayerIndex(); | |
| InitializeActions(); | |
| AssignUserAndDevices(); | |
| ActivateInput(); | |
| } | |
| // Split-screen index defaults to player index. | |
| if (s_InitSplitScreenIndex >= 0) | |
| m_SplitScreenIndex = splitScreenIndex; | |
| else | |
| m_SplitScreenIndex = playerIndex; | |
| // Add to global list and sort it by player index. | |
| ArrayHelpers.AppendWithCapacity(ref s_AllActivePlayers, ref s_AllActivePlayersCount, this); | |
| for (var i = 1; i < s_AllActivePlayersCount; ++i) | |
| for (var j = i; j > 0 && s_AllActivePlayers[j - 1].playerIndex > s_AllActivePlayers[j].playerIndex; --j) | |
| s_AllActivePlayers.SwapElements(j, j - 1); | |
| // If it's the first player, hook into user change notifications. | |
| if (s_AllActivePlayersCount == 1) | |
| { | |
| if (s_UserChangeDelegate == null) | |
| s_UserChangeDelegate = OnUserChange; | |
| InputUser.onChange += s_UserChangeDelegate; | |
| } | |
| // In single player, set up for automatic device switching. | |
| if (isSinglePlayer) | |
| { | |
| if (m_Actions != null && m_Actions.controlSchemes.Count == 0) | |
| { | |
| // No control schemes. We pick up whatever is compatible with the bindings | |
| // we have. | |
| StartListeningForDeviceChanges(); | |
| } | |
| else if (!neverAutoSwitchControlSchemes) | |
| { | |
| // We have control schemes so we only listen for unpaired device *input*, i.e. | |
| // actual use of an unpaired device (as opposed to it merely getting plugged in). | |
| StartListeningForUnpairedDeviceActivity(); | |
| } | |
| } | |
| HandleControlsChanged(); | |
| // Trigger join event. | |
| PlayerInputManager.instance?.NotifyPlayerJoined(this); | |
| } | |
| private void StartListeningForUnpairedDeviceActivity() | |
| { | |
| if (m_OnUnpairedDeviceUsedHooked) | |
| return; | |
| if (m_UnpairedDeviceUsedDelegate == null) | |
| m_UnpairedDeviceUsedDelegate = OnUnpairedDeviceUsed; | |
| if (m_PreFilterUnpairedDeviceUsedDelegate == null) | |
| m_PreFilterUnpairedDeviceUsedDelegate = OnPreFilterUnpairedDeviceUsed; | |
| InputUser.onUnpairedDeviceUsed += m_UnpairedDeviceUsedDelegate; | |
| InputUser.onPrefilterUnpairedDeviceActivity += m_PreFilterUnpairedDeviceUsedDelegate; | |
| ++InputUser.listenForUnpairedDeviceActivity; | |
| m_OnUnpairedDeviceUsedHooked = true; | |
| } | |
| private void StopListeningForUnpairedDeviceActivity() | |
| { | |
| if (!m_OnUnpairedDeviceUsedHooked) | |
| return; | |
| InputUser.onUnpairedDeviceUsed -= m_UnpairedDeviceUsedDelegate; | |
| InputUser.onPrefilterUnpairedDeviceActivity -= m_PreFilterUnpairedDeviceUsedDelegate; | |
| --InputUser.listenForUnpairedDeviceActivity; | |
| m_OnUnpairedDeviceUsedHooked = false; | |
| } | |
| private void StartListeningForDeviceChanges() | |
| { | |
| if (m_OnDeviceChangeHooked) | |
| return; | |
| if (m_DeviceChangeDelegate == null) | |
| m_DeviceChangeDelegate = OnDeviceChange; | |
| InputSystem.onDeviceChange += m_DeviceChangeDelegate; | |
| m_OnDeviceChangeHooked = true; | |
| } | |
| private void StopListeningForDeviceChanges() | |
| { | |
| if (!m_OnDeviceChangeHooked) | |
| return; | |
| InputSystem.onDeviceChange -= m_DeviceChangeDelegate; | |
| m_OnDeviceChangeHooked = false; | |
| } | |
| private void OnDisable() | |
| { | |
| m_Enabled = false; | |
| // Remove from global list. | |
| var index = ArrayHelpers.IndexOfReference(s_AllActivePlayers, this, s_AllActivePlayersCount); | |
| if (index != -1) | |
| ArrayHelpers.EraseAtWithCapacity(s_AllActivePlayers, ref s_AllActivePlayersCount, index); | |
| // Unhook from change notifications if we're the last player. | |
| if (s_AllActivePlayersCount == 0 && s_UserChangeDelegate != null) | |
| InputUser.onChange -= s_UserChangeDelegate; | |
| StopListeningForUnpairedDeviceActivity(); | |
| StopListeningForDeviceChanges(); | |
| // Trigger leave event. | |
| PlayerInputManager.instance?.NotifyPlayerLeft(this); | |
| ////TODO: ideally, this shouldn't have to resolve at all and instead wait for someone to need the updated setup | |
| // Avoid re-resolving bindings over and over while we disassemble | |
| // the configuration. | |
| using (InputActionRebindingExtensions.DeferBindingResolution()) | |
| { | |
| DeactivateInput(); | |
| UnassignUserAndDevices(); | |
| UninitializeActions(); | |
| } | |
| m_PlayerIndex = -1; | |
| } | |
| // ReSharper disable once UnusedMember.Global | |
| /// <summary> | |
| /// Debug helper method that can be hooked up to actions when using <see cref="UnityEngine.InputSystem.PlayerNotifications.InvokeUnityEvents"/>. | |
| /// </summary> | |
| public void DebugLogAction(InputAction.CallbackContext context) | |
| { | |
| Debug.Log(context.ToString()); | |
| } | |
| private void HandleDeviceLost() | |
| { | |
| switch (m_NotificationBehavior) | |
| { | |
| case PlayerNotifications.SendMessages: | |
| SendMessage(DeviceLostMessage, this, SendMessageOptions.DontRequireReceiver); | |
| break; | |
| case PlayerNotifications.BroadcastMessages: | |
| BroadcastMessage(DeviceLostMessage, this, SendMessageOptions.DontRequireReceiver); | |
| break; | |
| case PlayerNotifications.InvokeUnityEvents: | |
| m_DeviceLostEvent?.Invoke(this); | |
| break; | |
| case PlayerNotifications.InvokeCSharpEvents: | |
| DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceLostCallbacks, this, "onDeviceLost"); | |
| break; | |
| } | |
| } | |
| private void HandleDeviceRegained() | |
| { | |
| switch (m_NotificationBehavior) | |
| { | |
| case PlayerNotifications.SendMessages: | |
| SendMessage(DeviceRegainedMessage, this, SendMessageOptions.DontRequireReceiver); | |
| break; | |
| case PlayerNotifications.BroadcastMessages: | |
| BroadcastMessage(DeviceRegainedMessage, this, SendMessageOptions.DontRequireReceiver); | |
| break; | |
| case PlayerNotifications.InvokeUnityEvents: | |
| m_DeviceRegainedEvent?.Invoke(this); | |
| break; | |
| case PlayerNotifications.InvokeCSharpEvents: | |
| DelegateHelpers.InvokeCallbacksSafe(ref m_DeviceRegainedCallbacks, this, "onDeviceRegained"); | |
| break; | |
| } | |
| } | |
| private void HandleControlsChanged() | |
| { | |
| switch (m_NotificationBehavior) | |
| { | |
| case PlayerNotifications.SendMessages: | |
| SendMessage(ControlsChangedMessage, this, SendMessageOptions.DontRequireReceiver); | |
| break; | |
| case PlayerNotifications.BroadcastMessages: | |
| BroadcastMessage(ControlsChangedMessage, this, SendMessageOptions.DontRequireReceiver); | |
| break; | |
| case PlayerNotifications.InvokeUnityEvents: | |
| m_ControlsChangedEvent?.Invoke(this); | |
| break; | |
| case PlayerNotifications.InvokeCSharpEvents: | |
| DelegateHelpers.InvokeCallbacksSafe(ref m_ControlsChangedCallbacks, this, "onControlsChanged"); | |
| break; | |
| } | |
| } | |
| private static void OnUserChange(InputUser user, InputUserChange change, InputDevice device) | |
| { | |
| switch (change) | |
| { | |
| case InputUserChange.DeviceLost: | |
| case InputUserChange.DeviceRegained: | |
| for (var i = 0; i < s_AllActivePlayersCount; ++i) | |
| { | |
| var player = s_AllActivePlayers[i]; | |
| if (player.m_InputUser == user) | |
| { | |
| if (change == InputUserChange.DeviceLost) | |
| player.HandleDeviceLost(); | |
| else if (change == InputUserChange.DeviceRegained) | |
| player.HandleDeviceRegained(); | |
| } | |
| } | |
| break; | |
| case InputUserChange.ControlsChanged: | |
| for (var i = 0; i < s_AllActivePlayersCount; ++i) | |
| { | |
| var player = s_AllActivePlayers[i]; | |
| if (player.m_InputUser == user) | |
| player.HandleControlsChanged(); | |
| } | |
| break; | |
| } | |
| } | |
| private static bool OnPreFilterUnpairedDeviceUsed(InputDevice device, InputEventPtr eventPtr) | |
| { | |
| // Early out if the device isn't usable with any of our control schemes. | |
| var actions = all[0].actions; | |
| return actions != null && actions.IsUsableWithDevice(device); | |
| } | |
| private void OnUnpairedDeviceUsed(InputControl control, InputEventPtr eventPtr) | |
| { | |
| // We only support automatic control scheme switching in single player mode. | |
| // OnEnable() should automatically unhook us. | |
| if (!isSinglePlayer || neverAutoSwitchControlSchemes) | |
| return; | |
| var player = all[0]; | |
| var actions = player.m_Actions; | |
| if (actions == null) | |
| return; | |
| var device = control.device; | |
| using (InputActionRebindingExtensions.DeferBindingResolution()) | |
| using (var availableDevices = InputUser.GetUnpairedInputDevices()) | |
| { | |
| // Put our device first in the list to make sure it's the first one picked for a match. | |
| if (availableDevices.Count > 1) | |
| { | |
| var indexOfDevice = availableDevices.IndexOf(device); | |
| Debug.Assert(indexOfDevice != -1, "Did not find unpaired device in list of unpaired devices"); | |
| availableDevices.SwapElements(0, indexOfDevice); | |
| } | |
| // Add all devices currently already paired to us. This avoids us preventing | |
| // control schemes switches because of devices we're looking for already being | |
| // paired to us. | |
| var currentDevices = player.devices; | |
| for (var i = 0; i < currentDevices.Count; ++i) | |
| availableDevices.Add(currentDevices[i]); | |
| // Find the best control scheme to use. | |
| if (InputControlScheme.FindControlSchemeForDevices(availableDevices, player.m_Actions.controlSchemes, | |
| out var controlScheme, out var matchResult, mustIncludeDevice: device)) | |
| { | |
| try | |
| { | |
| // First remove the currently paired devices. | |
| var userValid = player.user.valid; | |
| if (userValid) | |
| player.user.UnpairDevices(); | |
| // Then pair devices that we've picked according to the control scheme. | |
| var newDevices = matchResult.devices; | |
| Debug.Assert(newDevices.Count > 0, "Expecting to see at least one device here"); | |
| for (var i = 0; i < newDevices.Count; ++i) | |
| { | |
| player.m_InputUser = InputUser.PerformPairingWithDevice(newDevices[i], user: player.m_InputUser); | |
| if (!userValid && player.actions != null) | |
| player.m_InputUser.AssociateActionsWithUser(player.actions); | |
| } | |
| // And finally switch to the new control scheme. | |
| player.user.ActivateControlScheme(controlScheme); | |
| } | |
| finally | |
| { | |
| matchResult.Dispose(); | |
| } | |
| } | |
| } | |
| } | |
| private void OnDeviceChange(InputDevice device, InputDeviceChange change) | |
| { | |
| // If a device was added and we have no control schemes in the actions and we're in | |
| // single-player mode, pair the device to the player if it works with the bindings we have. | |
| if (change == InputDeviceChange.Added && | |
| isSinglePlayer && | |
| m_Actions != null && m_Actions.controlSchemes.Count == 0 && | |
| HaveBindingForDevice(device) && | |
| m_InputUser.valid) | |
| { | |
| InputUser.PerformPairingWithDevice(device, user: m_InputUser); | |
| } | |
| } | |
| private void SwitchControlSchemeInternal(ref InputControlScheme controlScheme, params InputDevice[] devices) | |
| { | |
| Debug.Assert(devices != null); | |
| // Note that we are doing two somwhat uncorrelated actions here: | |
| // - Switching control scheme | |
| // - Explicitly pairing with given devices regardless if making sense with respect to control scheme | |
| using (InputActionRebindingExtensions.DeferBindingResolution()) | |
| { | |
| // Unpair device previously paired but not part of given devices to pair with | |
| for (var i = user.pairedDevices.Count - 1; i >= 0; --i) | |
| { | |
| if (!devices.ContainsReference(user.pairedDevices[i])) | |
| user.UnpairDevice(user.pairedDevices[i]); | |
| } | |
| // Pair devices not previously paired but that are part of given devices to pair with | |
| foreach (var device in devices) | |
| { | |
| if (!user.pairedDevices.ContainsReference(device)) | |
| InputUser.PerformPairingWithDevice(device, user: user); | |
| } | |
| // Only activate control scheme if its a different scheme | |
| if (!user.controlScheme.HasValue || !user.controlScheme.Value.Equals(controlScheme)) | |
| user.ActivateControlScheme(controlScheme); | |
| } | |
| } | |
| [] | |
| public class ActionEvent : UnityEvent<InputAction.CallbackContext> | |
| { | |
| public string actionId => m_ActionId; | |
| public string actionName => m_ActionName; | |
| [] private string m_ActionId; | |
| [] private string m_ActionName; | |
| public ActionEvent() | |
| { | |
| } | |
| public ActionEvent(InputAction action) | |
| { | |
| if (action == null) | |
| throw new ArgumentNullException(nameof(action)); | |
| if (action.isSingletonAction) | |
| throw new ArgumentException($"Action must be part of an asset (given action '{action}' is a singleton)"); | |
| if (action.actionMap.asset == null) | |
| throw new ArgumentException($"Action must be part of an asset (given action '{action}' is not)"); | |
| m_ActionId = action.id.ToString(); | |
| m_ActionName = $"{action.actionMap.name}/{action.name}"; | |
| } | |
| public ActionEvent(Guid actionGUID, string name = null) | |
| { | |
| m_ActionId = actionGUID.ToString(); | |
| m_ActionName = name; | |
| } | |
| } | |
| /// <summary> | |
| /// Event that is triggered when an <see cref="InputDevice"/> paired to a <see cref="PlayerInput"/> is disconnected. | |
| /// </summary> | |
| /// <seealso cref="deviceLostEvent"/> | |
| [] | |
| public class DeviceLostEvent : UnityEvent<PlayerInput> | |
| { | |
| } | |
| /// <summary> | |
| /// Event that is triggered when a <see cref="PlayerInput"/> regains an <see cref="InputDevice"/> previously lost. | |
| /// </summary> | |
| /// <seealso cref="deviceRegainedEvent"/> | |
| [] | |
| public class DeviceRegainedEvent : UnityEvent<PlayerInput> | |
| { | |
| } | |
| /// <summary> | |
| /// Event that is triggered when the set of controls used by a <see cref="PlayerInput"/> changes. | |
| /// </summary> | |
| /// <seealso cref="controlsChangedEvent"/> | |
| [] | |
| public class ControlsChangedEvent : UnityEvent<PlayerInput> | |
| { | |
| } | |
| } | |
| } | |