# 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(