Gamepad
Gamepads reach React through built-ins that are typed in every generated
bevy.ts, with no Rust code or registration: three
events (gamepadConnected,
gamepadDisconnected, gamepadInput), the bevy.gamepad.getAll()
request, and the
bevy.gamepad.rumble() and bevy.gamepad.stopRumble()
messages.
Usage
import { useEffect } from "react";
import { bevy } from "./bevy"; // generated
function Menu({ onConfirm }: { onConfirm: () => void }) {
useEffect(
() =>
bevy.on("gamepadInput", (batch) => {
for (const b of batch.buttons) {
if (b.button === "south" && b.pressed) onConfirm();
}
}),
[onConfirm],
);
return <node>{/* … */}</node>;
}Pads are detected through Bevy's gamepad backend, the bevy_gilrs cargo
feature, which is part of Bevy's default features. Without it no pad ever
connects.
Connected pads
gamepadConnected carries a GamepadConnectedData:
| Field | Type | Meaning |
|---|---|---|
gamepad |
number |
The pad's id, used by every other gamepad API |
name |
string |
The device name the OS reports |
vendorId |
number | null |
USB vendor id, when known |
productId |
number | null |
USB product id, when known |
gamepadDisconnected carries { gamepad }, the id the pad was announced
under.
- Ids count up and are never reused: a pad that reconnects gets a new id, so a stale id never refers to another device.
- Pads connected before the app started are announced once React's first render has been applied, before any of their input.
bevy.gamepad.getAll()resolves with every connected pad (GamepadConnectedData[],[]when none). Components that mount after a pad connected have missed its event; seed from the request, then follow the events:
import { useEffect, useState } from "react";
import { bevy, type GamepadConnectedData } from "./bevy";
function usePads() {
const [pads, setPads] = useState<GamepadConnectedData[]>([]);
useEffect(() => {
void bevy.gamepad.getAll().then(setPads);
const offConnect = bevy.on("gamepadConnected", (p) =>
setPads((ps) => [...ps, p]),
);
const offDisconnect = bevy.on("gamepadDisconnected", (p) =>
setPads((ps) => ps.filter((x) => x.gamepad !== p.gamepad)),
);
return () => {
offConnect();
offDisconnect();
};
}, []);
return pads;
}Input
gamepadInput fires at most once per frame, and only in frames where some
input changed. Its buttons and axes arrays list every change of the
frame across all pads, in order:
- A button change is
{ gamepad, button, pressed, value }:valueis0..1(analog triggers use the whole range),pressedis the digital state after the change. - An axis change is
{ gamepad, axis, value }, withvaluein-1..1. Sticks follow Bevy's convention, up is+1, the inverse of the web Gamepad API. - Changes are filtered by Bevy's
GamepadSettings(dead zones and press thresholds) before they are reported. An idle pad sends nothing. - Every analog change is a separate entry, and
pressedstaystrueacross all the changes of a held trigger. To act once per press, compare against the previous state yourself. - Input arriving before React's first render has been applied is dropped.
Button and axis names are Bevy's, camelCased:
| Kind | Names |
|---|---|
| Buttons | south, east, north, west, c, z, leftTrigger, leftTrigger2, rightTrigger, rightTrigger2, select, start, mode, leftThumb, rightThumb, dPadUp, dPadDown, dPadLeft, dPadRight |
| Axes | leftStickX, leftStickY, leftZ, rightStickX, rightStickY, rightZ |
south is the bottom face button (A on Xbox pads, Cross on PlayStation),
leftTrigger the left bumper and leftTrigger2 the analog left trigger;
leftThumb is a stick click. Non-standard buttons and axes arrive as
{ other: n }.
Rumble
bevy.gamepad.rumble({
gamepad: pad.gamepad,
duration: 200, // milliseconds
strongMotor: 0.4,
weakMotor: 0.6,
});
bevy.gamepad.stopRumble({ gamepad: pad.gamepad });durationis in milliseconds; negative values count as0.strongMotor(low frequency) andweakMotor(high frequency) are intensities, clamped to0..1. All four fields are required.- Overlapping rumbles add up rather than replace each other. Call
stopRumblefirst to replace a running effect. stopRumblestops every rumble on the pad.- An unknown id is ignored with a warning in the Bevy log. Rumble support varies by platform and pad.
Limits
- No built-in focus navigation: moving through menus with a pad is up to your React code.
- Pad ids change on every reconnect.
- Rumble is best-effort and fire-and-forget: React gets no confirmation.
The built-in types are listed in the generated
bevy.ts reference.
bevy-react