Over half of PC players on Steam use a controller at least some of the time, and Steam Deck players expect it by default. Proper Unity controller support means more than reading a stick: the UI has to be navigable, dead zones need tuning, rumble should feel right, and button prompts must change when the player switches from keyboard to an Xbox, PlayStation or Switch pad. This guide covers all of it with the Input System package in Unity 6.
Quick answer
Unity controller support comes from the Input System package: Xbox, PlayStation (DualShock 4, DualSense) and Switch Pro controllers work without plugins. Add gamepad bindings next to the keyboard ones in each action (Left Stick for Move, Button South for Jump), add the Input System UI Input Module so menus can be navigated with the stick, and tune stick dead zones.
Which Gamepads Unity Supports
The Input System recognises the common controllers out of the box and maps them all to a shared Gamepad layout. That’s the key idea: you bind to positions, not labels. buttonSouth is A on Xbox, Cross on PlayStation and B on a Switch Pro controller, because it’s the bottom face button on all of them.
- Xbox controllers (XInput on Windows, plus macOS support) appear as
XInputControlleror a derived layout. - PlayStation DualShock 4 and DualSense appear as
DualShockGamepadsubclasses, includingDualSenseGamepadHIDon desktop. - Switch Pro controllers are supported over HID on desktop as
SwitchProControllerHID. - Unknown pads may show up as a generic HID device without the Gamepad layout. Steam Input can translate those into an Xbox-style pad for you.
Make sure Project Settings > Player > Active Input Handling is set to Input System Package (New) or Both. Then open Window > Analysis > Input Debugger with the pad plugged in. If it isn’t listed there, no script will see it. New to actions? Start with our beginner’s guide to the Input System.
Action Maps for Keyboard and Gamepad
Create two control schemes in your Input Action Asset: “Keyboard&Mouse” (requires Keyboard and Mouse) and “Gamepad” (requires Gamepad). Then give each action one binding per scheme:
- Move (Value, Vector2): WASD composite plus
<Gamepad>/leftStick. - Look (Value, Vector2):
<Mouse>/deltaplus<Gamepad>/rightStick. - Jump (Button): Space plus
<Gamepad>/buttonSouth. - Fire (Button): Left mouse plus
<Gamepad>/rightTrigger.
One gotcha: mouse delta and a stick are very different. Mouse delta is pixels moved this frame and should not be multiplied by Time.deltaTime; a stick is a -1 to 1 rate that should be. Handle them separately:
using UnityEngine;
using UnityEngine.InputSystem;
public class LookInput : MonoBehaviour
{
[SerializeField] InputActionReference look;
[SerializeField] float mouseSensitivity = 0.1f;
[SerializeField] float stickDegreesPerSecond = 180f;
public Vector2 GetLookDelta()
{
Vector2 v = look.action.ReadValue<Vector2>();
bool fromMouse = look.action.activeControl?.device is Mouse;
return fromMouse ? v * mouseSensitivity
: v * stickDegreesPerSecond * Time.deltaTime;
}
}
Alternatively, split them into two actions (LookMouse and LookStick), which is clearer and lets you add a sensitivity processor to each in the asset.
Dead Zones and Stick Feel
Every stick drifts a little at rest. A dead zone ignores the small values near the centre. The Input System applies a Stick Deadzone processor with a radial dead zone, using the defaults in Project Settings > Input System Package: Default Deadzone Min (0.125) and Max (0.925). Max matters too, since some sticks never quite reach 1.0 at full tilt, and the processor rescales so they do.
To tune per binding, select the stick binding in the asset and add a Stick Deadzone processor with your own values. A few rules of thumb:
- 0.1 to 0.2 min for movement. Lower feels responsive but risks drift on worn pads.
- Radial, not per-axis. Per-axis dead zones make diagonal aim feel sticky and snap to the cardinal directions.
- Add a response curve for aiming. Squaring the magnitude gives fine control near the centre and speed at full tilt:
Vector2 ApplyCurve(Vector2 stick)
{
float m = stick.magnitude;
return m < 0.0001f ? Vector2.zero : stick / m * (m * m);
}
Expose sensitivity and inversion in your settings menu. Invert Y is still a must for a lot of players. Triggers have a press point for Button actions (Default Button Press Point, 0.5). If firing feels late, lower it on the trigger binding with a Press interaction.
Gamepad UI Navigation
For menus, the EventSystem must use the InputSystemUIInputModule. Its default actions already map the left stick and D-pad to Navigate, the south button to Submit and the east button to Cancel. What breaks gamepad UI almost every time is that nothing is selected:
- Set First Selected on the EventSystem.
- Whenever you open a panel, call
EventSystem.current.SetSelectedGameObject(firstButton). - When closing a panel, return selection to the button that opened it.
- Give buttons a clearly visible Selected state, since there’s no mouse cursor to show focus.
Our main menu tutorial walks through this for a full menu with a settings panel. Sliders respond to left and right navigation automatically, and dropdowns open with Submit.
Rumble (Haptics)
Rumble is one line, plus remembering to stop it. SetMotorSpeeds(low, high) drives the heavy low-frequency motor and the light high-frequency motor, each from 0 to 1:
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
public class Rumble : MonoBehaviour
{
Coroutine current;
public void Pulse(float low, float high, float duration)
{
var pad = Gamepad.current;
if (pad == null) return;
if (current != null) StopCoroutine(current);
current = StartCoroutine(Run(pad, low, high, duration));
}
IEnumerator Run(Gamepad pad, float low, float high, float duration)
{
pad.SetMotorSpeeds(low, high);
yield return new WaitForSecondsRealtime(duration);
pad.SetMotorSpeeds(0f, 0f);
current = null;
}
void OnApplicationPause(bool paused)
{
if (paused) InputSystem.PauseHaptics(); else InputSystem.ResumeHaptics();
}
void OnDisable() => InputSystem.ResetHaptics();
}
Use short, light pulses for hits (0.2 to 0.4, about 0.1 s) and longer heavy ones for explosions. Always call InputSystem.ResetHaptics() when pausing or quitting to a menu; a controller left buzzing is a bug players notice instantly. Provide a rumble toggle in settings. In local multiplayer, rumble the specific player’s device from PlayerInput.devices rather than Gamepad.current.
Device Switching and Button Prompts
Players switch mid-game, so prompts must update live. The easiest hook is PlayerInput: with Auto-Switch enabled (single player), it changes control scheme when another device is used and fires onControlsChanged:
using System;
using UnityEngine;
using UnityEngine.InputSystem;
public enum PromptStyle { Keyboard, Xbox, PlayStation, Switch }
public class PromptManager : MonoBehaviour
{
public static event Action<PromptStyle> StyleChanged;
public static PromptStyle Current { get; private set; }
[SerializeField] PlayerInput playerInput;
void OnEnable() => playerInput.onControlsChanged += OnControlsChanged;
void OnDisable() => playerInput.onControlsChanged -= OnControlsChanged;
void Start() => OnControlsChanged(playerInput);
void OnControlsChanged(PlayerInput pi)
{
PromptStyle style = PromptStyle.Keyboard;
foreach (var device in pi.devices)
{
if (device is UnityEngine.InputSystem.DualShock.DualShockGamepad) style = PromptStyle.PlayStation;
else if (device is UnityEngine.InputSystem.Switch.SwitchProControllerHID) style = PromptStyle.Switch;
else if (device is Gamepad) style = PromptStyle.Xbox;
}
Current = style;
StyleChanged?.Invoke(style);
}
}
Each prompt UI element subscribes to StyleChanged and swaps its sprite. Store the sprite sets in ScriptableObjects keyed by control path (buttonSouth, rightTrigger, and so on) so prompts follow rebinds too. GetBindingDisplayString on the action gives you the control path for the current binding; our Input System rebinding guide shows that lookup.
Without PlayerInput, listen to InputSystem.onDeviceChange for plug and unplug events, and use action.activeControl.device when an action performs to detect which device the player is actually using. When a controller disconnects mid-game, pause and show a “reconnect controller” message rather than letting the character run off a cliff.
A note on Switch: the Switch Pro controller has A and B swapped compared with Xbox. Because Unity binds by position, Confirm on buttonSouth is the Switch “B” button. That’s correct for PC ports, but if you ship on Nintendo hardware, follow the platform’s own Confirm and Cancel convention.
💻 Testing on Steam Deck and controllers?
A laptop with a decent GPU lets you build, test and tweak controller feel anywhere. Our budget picks all run Unity 6 comfortably.
Troubleshooting
Gamepad does nothing at all
Open the Input Debugger. If the device isn’t listed, it’s a driver, cable or Bluetooth issue. If it’s listed but actions don’t fire, check that the action map is enabled and that your control scheme’s device requirements include Gamepad.
Character drifts slowly without touching the stick
The dead zone is too small for that controller. Raise Default Deadzone Min or add a Stick Deadzone processor at around 0.15 to 0.2.
Camera spins wildly on the stick but is fine with the mouse
You’re treating both inputs the same. Multiply stick input by Time.deltaTime and a degrees-per-second value; don’t do that for mouse delta.
UI ignores the gamepad
No object is selected, or the EventSystem still uses the old Standalone Input Module. Replace it with InputSystemUIInputModule and set a selection.
Controller keeps rumbling after pausing
The coroutine stopping the motors uses scaled time and never resumes at timeScale 0. Use WaitForSecondsRealtime and call InputSystem.ResetHaptics() on pause.
FAQ
Do PlayStation controllers work in Unity without extra plugins?
Yes. DualShock 4 and DualSense are supported on Windows and macOS through the Input System. Some features, like adaptive triggers, need console SDKs or third-party plugins.
Does Steam Input interfere with Unity?
It can. Steam may present every controller as an Xbox pad, which is fine for input but means your PlayStation prompts never show. Steamworks APIs can report the real controller type.
How do I support local multiplayer with several gamepads?
Use PlayerInputManager with a PlayerInput prefab. Each joining player gets their own device and action instance automatically.
Should I use Gamepad.current directly?
It’s fine for quick prototypes and rumble in single player, but actions are better for gameplay because they support rebinding, multiple devices and control schemes.
Can I detect a Switch Joy-Con?
Joy-Cons on PC aren’t supported out of the box the way the Pro controller is. Steam Input or a third-party plugin is the usual route.
Reader questions
Asked by readers of this article and answered by AI. Behavior can differ between Unity versions, so test in yours.




Leave a Reply