Quick answer: Check capability first (joystick.rumble(0, 0, 0) returns False on unsupported). Use pygame._sdl2.controller.Controller for better cross-pad rumble support.

A combat game triggers controller rumble on hit. Most players feel it; a few don’t. Generic third-party pads don’t support rumble at all; Pygame quietly does nothing.

Capability Check

js = pygame.joystick.Joystick(0)
js.init()
supports = js.rumble(0.0, 0.0, 0)   # test call
if not supports:
    print("Controller doesn't support rumble")

rumble() returns True if the device accepted the request, False otherwise. Use this to gate UI hints (“Vibration: unsupported”).

Triggering Rumble

low_freq = 0.6   # 0-1
high_freq = 0.4
duration_ms = 200
js.rumble(low_freq, high_freq, duration_ms)

Low/high split is for dualshock-style two-motor pads. Single-motor pads use the higher of the two.

SDL2 Controller API

from pygame._sdl2 import controller

controller.init()
if controller.is_controller(0):
    pad = controller.Controller(0)
    pad.rumble(0.6, 0.4, 200)

SDL2 controller wraps known pads (Xbox, DS4, DS5, Switch). Rumble works more reliably than the legacy Joystick API.

User Setting

Always have a settings toggle. Some users find rumble irritating; some controllers misreport support. Persist preference:

if settings.rumble_enabled and pad.rumble(...):
    rumble_done = True
else:
    # silent fallback
    pass

Verifying

Test on Xbox, DS4, generic pad. Officially-supported feel rumble; unsupported get no feedback but no crash either. Setting toggles cleanly.

“Rumble support is per-device. Probe, fall back gracefully, give users a toggle.”

For Steam Deck specifically, rumble works via the Controller API but with subtle differences in motor curves — test on Deck explicitly before shipping.