123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031 |
- using System;
- using System.Collections.Generic;
- using UnityEngine.Events;
- using UnityEngine.InputSystem.LowLevel;
- using UnityEngine.InputSystem.Users;
- using UnityEngine.InputSystem.Utilities;
-
- #if UNITY_EDITOR
- using UnityEngine.InputSystem.Editor;
- #endif
-
- #if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI
- using UnityEngine.InputSystem.UI;
- #endif
-
- ////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"/>
- [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces")]
- [AddComponentMenu("Input/Player Input")]
- [DisallowMultipleComponent]
- [HelpURL(InputSystem.kDocUrl + "/manual/PlayerInput.html")]
- 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;
-
- [Obsolete("Use inputIsActive instead.")]
- 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
- #if UNITY_EDITOR
- // camera property is deprecated and only available in Editor.
- new
- #endif
- Camera camera
- {
- get => m_Camera;
- set => m_Camera = value;
- }
-
- #if PACKAGE_DOCS_GENERATION || UNITY_INPUT_SYSTEM_ENABLE_UI
- /// <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;
- }
- }
- #endif
-
- /// <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;
- }
-
- [Obsolete("Use DeactivateInput instead.")]
- 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;
- }
-
- [Tooltip("Input actions associated with the player.")]
- [SerializeField] internal InputActionAsset m_Actions;
- [Tooltip("Determine how notifications should be sent when an input-related event associated with the player happens.")]
- [SerializeField] internal PlayerNotifications m_NotificationBehavior;
- [Tooltip("UI InputModule that should have it's input actions synchronized to this PlayerInput's actions.")]
-
- #if UNITY_INPUT_SYSTEM_ENABLE_UI
- [SerializeField] internal InputSystemUIInputModule m_UIInputModule;
- [Tooltip("Event that is triggered when the PlayerInput loses a paired device (e.g. its battery runs out).")]
- #endif
-
- [SerializeField] internal DeviceLostEvent m_DeviceLostEvent;
- [SerializeField] internal DeviceRegainedEvent m_DeviceRegainedEvent;
- [SerializeField] internal ControlsChangedEvent m_ControlsChangedEvent;
- [SerializeField] internal ActionEvent[] m_ActionEvents;
- [SerializeField] internal bool m_NeverAutoSwitchControlSchemes;
- [SerializeField] internal string m_DefaultControlScheme;////REVIEW: should we have IDs for these so we can rename safely?
- [SerializeField] internal string m_DefaultActionMap;
- [SerializeField] internal int m_SplitScreenIndex = -1;
- [Tooltip("Reference to the player's view camera. Note that this is only required when using split-screen and/or "
- + "per-player UIs. Otherwise it is safe to leave this property uninitialized.")]
- [SerializeField] 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.
- [NonSerialized] private InputValue m_InputValueObject;
-
- [NonSerialized] internal InputActionMap m_CurrentActionMap;
-
- [NonSerialized] private int m_PlayerIndex = -1;
- [NonSerialized] private bool m_InputActive;
- [NonSerialized] private bool m_Enabled;
- [NonSerialized] internal bool m_ActionsInitialized;
- [NonSerialized] private Dictionary<string, string> m_ActionMessageNames;
- [NonSerialized] private InputUser m_InputUser;
- [NonSerialized] private Action<InputAction.CallbackContext> m_ActionTriggeredDelegate;
- [NonSerialized] private CallbackArray<Action<PlayerInput>> m_DeviceLostCallbacks;
- [NonSerialized] private CallbackArray<Action<PlayerInput>> m_DeviceRegainedCallbacks;
- [NonSerialized] private CallbackArray<Action<PlayerInput>> m_ControlsChangedCallbacks;
- [NonSerialized] private CallbackArray<Action<InputAction.CallbackContext>> m_ActionTriggeredCallbacks;
- [NonSerialized] private Action<InputControl, InputEventPtr> m_UnpairedDeviceUsedDelegate;
- [NonSerialized] private Func<InputDevice, InputEventPtr, bool> m_PreFilterUnpairedDeviceUsedDelegate;
- [NonSerialized] private bool m_OnUnpairedDeviceUsedHooked;
- [NonSerialized] private Action<InputDevice, InputDeviceChange> m_DeviceChangeDelegate;
- [NonSerialized] 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 UNITY_INPUT_SYSTEM_ENABLE_UI
- if (uiInputModule != null)
- uiInputModule.actionsAsset = m_Actions;
- #endif
-
- 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;
- }
- }
-
- #if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
- void Reset()
- {
- // Set default actions to project wide actions.
- m_Actions = InputSystem.actions;
- // TODO Need to monitor changes?
- }
-
- #endif
-
- 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);
- }
- }
-
- [Serializable]
- public class ActionEvent : UnityEvent<InputAction.CallbackContext>
- {
- public string actionId => m_ActionId;
- public string actionName => m_ActionName;
-
- [SerializeField] private string m_ActionId;
- [SerializeField] 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"/>
- [Serializable]
- 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"/>
- [Serializable]
- 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"/>
- [Serializable]
- public class ControlsChangedEvent : UnityEvent<PlayerInput>
- {
- }
- }
- }
|