Orbit Camera
A reusable Bevy plugin for orbital camera control. Input handling is fully decoupled from camera logic via a trait, making it straightforward to bind any combination of keyboard, mouse, gamepad, or custom input.
- Rust
- Bevy
- ECS
Trait-Based Input
The plugin is generic over an OrbitCameraInput trait. Any type that implements this trait can drive the camera —
default mouse controls, a gamepad, keyboard arrows, or a custom input
reader that pulls from a network socket. The camera system itself
never touches Res<ButtonInput<KeyCode>> directly; it only sees normalised orbit/zoom/roll deltas.
This pattern means the same OrbitCameraPlugin<I> can be dropped into any project and configured for the project's
input conventions without forking or patching the plugin.
Quaternion Rotation
All rotations use quaternions exclusively — no Euler angles. This prevents gimbal lock regardless of orbit angle, which matters when the camera is used to orbit a planet where the user can reach any viewing angle including directly over the poles.
Two rotation modes are supported: GlobalY (turntable — yaw around the world up axis, pitch around the camera's
local X) and CameraRelative (free-look — both axes relative to the camera). The former is natural
for viewing a model; the latter for flying around a scene.
Speed Falloff
Zoom speed decreases as the camera approaches its minimum radius, providing a natural "resistance" feeling rather than abruptly hitting a hard stop. Orbit speed scales proportionally with the current radius so that a given mouse movement always sweeps the same arc length on the focus sphere — the camera feels equally responsive when zoomed in close or pulled back to a wide view.
Usage
Adding the camera to a Bevy app is a few lines:
app.add_plugins(OrbitCameraPlugin::<MouseInput>::default());
commands.spawn((
Camera3dBundle::default(),
OrbitCamera {
focus: Vec3::ZERO,
radius: 10.0,
..default()
},
));Swapping to a custom input scheme means changing the type parameter
and providing an implementation of OrbitCameraInput — the camera behaviour is unchanged.