# 2. Control Schemes Devices and Consumption Patterns Part of [[Input System]]. Previous: [[1. Package Setup and Action Foundations]]. Next: [[3. Runtime Rebinding]]. > [!warning] Verification status > Control-scheme editor behavior and PlayerInput workflow notes follow package docs (1.20 / 6.7 workflow pages). Generated class member names depend on your asset; treat `Player.Move` style access as illustrative. Scripts were **not** compiled in Unity. ## 2.1 Control schemes Docs: [Group bindings to control schemes](https://docs.unity3d.com/Packages/[email protected]/manual/group-binding-to-control-scheme.html). A **control scheme** is a named group of bindings plus optional **device requirements** (must have Keyboard, Mouse, Gamepad, etc.). Schemes let the system enable the binding set that matches what the player is actually using. Create schemes from the **Control Schemes** menu in the Actions Editor (top left): 1. **Keyboard&Mouse** — require Keyboard + Mouse. 2. **Gamepad** — require Gamepad. 3. **XR** — require XRController (optional when you share an asset with HMD work). Then select each binding (or composite **part**) and tick **Use in control scheme**. Keyboard WASD parts → Keyboard&Mouse only. Left stick → Gamepad only. That prevents stick and WASD from both contributing in confusing ways when **Both** backends or multiple devices are live. `PlayerInput` can auto-switch schemes based on last-used device. If you roll your own enabling, filter with binding masks / scheme names explicitly. ## 2.2 Device pairing and users At runtime the Input System tracks **devices** (`Keyboard.current`, `Gamepad.current`, XR controllers, etc.) and, for multiplayer, **users** (`UnityEngine.InputSystem.Users.InputUser`). High level: - Single-player desktop: you can ignore `InputUser` and just enable maps; the default project often "just works". - Local multiplayer: use `PlayerInput` + `PlayerInputManager`, or pair devices yourself with `InputUser.PerformPairingWithDevice`. - When a paired device disconnects, `InputUser` keeps the slot and re-pairs when the device returns — subscribe to `InputUser.onChange` ([Handle loss of a device](https://docs.unity3d.com/Packages/[email protected]/manual/handle-loss-of-device.html)). Never cache a device reference across domain reload / Play Mode cycles without re-resolving it (part 6). ## 2.3 Three consumption patterns Pick **one** primary pattern for the player pawn. Mixing all three in the same class is how projects get double-moved characters. ### A. Generated C# class (type-safe, code-first) On the `.inputed` Inspector, enable **Generate C# Class**, set namespace/class name, **Apply**. Docs overview: [Input action assets (1.19 manual still accurate for generation)](https://docs.unity3d.com/Packages/[email protected]/manual/ActionAssets.html). ```csharp using UnityEngine; using UnityEngine.InputSystem; /// <summary> /// Implements the generated IPlayerActions interface (name depends on your map). /// </summary> public sealed class PlayerViaGeneratedClass : MonoBehaviour, PlayerInputActions.IPlayerActions { PlayerInputActions _actions; Vector2 _move; Vector2 _look; void Awake() { _actions = new PlayerInputActions(); _actions.Player.AddCallbacks(this); // or SetCallbacks } void OnEnable() => _actions.Player.Enable(); void OnDisable() => _actions.Player.Disable(); public void OnMove(InputAction.CallbackContext context) => _move = context.ReadValue<Vector2>(); public void OnLook(InputAction.CallbackContext context) => _look = context.ReadValue<Vector2>(); public void OnJump(InputAction.CallbackContext context) { if (context.performed) Debug.Log("Jump"); } public void OnInteract(InputAction.CallbackContext context) { if (context.performed) Debug.Log("Interact"); } void Update() { // Use _move / _look for CharacterController or Rigidbody forces. transform.Translate(new Vector3(_move.x, 0f, _move.y) * (4f * Time.deltaTime), Space.Self); _ = _look; } } ``` **Pros:** rename-safe-ish via regeneration, great for [[Architecture and Code Patterns]] style pure C# systems. **Cons:** merge noise on the generated file; do not hand-edit it — reimport the asset instead. This is the pattern used in [[Unity New Input System Upgrade Tutorial]]. ### B. `PlayerInput` component (Inspector wiring + local co-op) Docs workflow: [Actions and the PlayerInput Component](https://docs.unity3d.com/Packages/[email protected]/manual/using-playerinput-workflow.html) (workflow text; APIs match 1.20). 1. Add **Player Input** to the player. 2. Assign the Actions asset. 3. Default Map = `Player`. 4. Behavior: **Send Messages**, **Invoke Unity Events**, or **Invoke CSharp Events**. ```csharp using UnityEngine; using UnityEngine.InputSystem; /// <summary> /// Wired from PlayerInput Invoke Unity Events (methods need CallbackContext). /// </summary> public sealed class PlayerViaPlayerInput : MonoBehaviour { Vector2 _move; public void OnMove(InputAction.CallbackContext context) { _move = context.ReadValue<Vector2>(); } public void OnJump(InputAction.CallbackContext context) { if (context.performed) Debug.Log("Jump"); } void Update() { transform.Translate(new Vector3(_move.x, 0f, _move.y) * (4f * Time.deltaTime), Space.Self); } } ``` **Critical:** if you also need the asset in code while using `PlayerInput`, read **`playerInput.actions`**, not a second `new MyActions()` and not a separate enable path that bypasses device filtering. Each `PlayerInput` keeps a **filtered copy** for its paired devices — required for split-screen / couch co-op and relevant to [[Multiplayer with Netcode for GameObjects]] only for *local* device ownership (Netcode still needs your own input → server RPC story). ### C. `InputActionReference` (ScriptableObject slots) Create references by dragging individual actions into `[SerializeField] InputActionReference` fields. Ideal for reusable components (a door that only cares about Interact) without depending on a generated class. ```csharp using UnityEngine; using UnityEngine.InputSystem; public sealed class InteractOnAction : MonoBehaviour { [SerializeField] InputActionReference interact; void OnEnable() { interact.action.performed += OnInteract; interact.action.Enable(); } void OnDisable() { interact.action.performed -= OnInteract; // Do not Disable() if a shared asset action is still needed elsewhere — // prefer map-level ownership in a single InputDirector. } void OnInteract(InputAction.CallbackContext ctx) { Debug.Log(quot;Interact by {ctx.control.device.displayName}", this); } } ``` **Ownership warning:** whoever `Enable()`s an action must be clear. Prefer one **InputDirector** / player brain that enables maps; other components only subscribe. ## 2.4 Project-wide actions vs asset instances Unity can keep **project-wide actions** (`InputSystem.actions`). Convenience for prototypes. For shipping games, an explicit asset on the player (or a bootstrap service) is easier to test and to swap per-control-scheme packs. If `PlayerInput` is in the scene, its assigned asset and UI module settings take priority for what gets enabled — see [UI Input Module introduction](https://docs.unity3d.com/Packages/[email protected]/manual/introduction-ui-input-module.html). ## 2.5 Choosing for Marshall's projects | Context | Recommendation | |---|---| | Teaching / Omnidirectional walker | Generated class (matches upgrade tutorial) | | Local multiplayer prototype | `PlayerInput` + Manager | | Shared systems (UI buttons, interactables) | `InputActionReference` + single enabler | | Quest locomotion / hands | Follow [[6.Quest 2 Input Hands and Passthrough]] action maps; keep desktop Player map separate or scheme-gated | | Diegetic / UI Toolkit | Part 4 + [[UI Toolkit and Diegetic UI]] / [[8.UI Toolkit for VR]] | Continue to rebinding once your consumption path is stable — rebinds mutate binding overrides on the live `InputActionAsset` instance your pattern owns.