Repository: bevyengine/bevy
File: crates/bevy_camera_controller/src/free_cam.rs (Rust)

Code before:
///
/// Use the [`FreeCam`] struct to add and customize the controller for a camera entity.
/// The camera's dynamic state is managed by the [`FreeCamState`] struct.
pub struct FreeCamPlugin;

impl Plugin for FreeCamPlugin {
    fn build(&self, app: &mut App) {
        // This ordering is required so that both fixed update and update systems can see the results correctly
        app.add_systems(
            RunFixedMainLoop,
            run_freecam_controller.in_set(RunFixedMainLoopSystems::BeforeFixedMainLoop),
        );
    }
}

/// Scales mouse motion into yaw/pitch movement.
///
/// Based on Valorant's default sensitivity, not entirely sure why it is exactly 1.0 / 180.0,
/// but we're guessing it is a misunderstanding between degrees/radians and then sticking with
/// it because it felt nice.
const RADIANS_PER_DOT: f32 = 1.0 / 180.0;

/// Stores the settings for the FreeCam controller.
///
/// This struct holds the configuration for movement, key bindings, sensitivity, etc.
/// The settings in this struct are immutable during runtime, and should be used to customize camera controls.
///
/// Add this component to a [`Camera`] entity, along with the [`FreeCamState`] component to track the dynamic state,
/// and add the [`FreeCamPlugin`] to your [`App`] to enable freecam controls.
#[derive(Component)]
pub struct FreeCam {
    /// Multiplier for pitch and yaw rotation speed.
    pub sensitivity: f32,
    /// [`KeyCode`] for forward translation.
    pub key_forward: KeyCode,
    /// [`KeyCode`] for backward translation.
    pub key_back: KeyCode,
    /// [`KeyCode`] for left translation.
    pub key_left: KeyCode,
    /// [`KeyCode`] for right translation.
    pub key_right: KeyCode,
    /// [`KeyCode`] for up translation.
    pub key_up: KeyCode,
    /// [`KeyCode`] for down translation.
    pub key_down: KeyCode,
    /// [`KeyCode`] to use [`run_speed`](FreeCam::run_speed) instead of
    /// [`walk_speed`](FreeCam::walk_speed) for translation.
    pub key_run: KeyCode,
    /// [`MouseButton`] for grabbing the mouse focus.
    pub mouse_key_cursor_grab: MouseButton,
    /// [`KeyCode`] for grabbing the keyboard focus.

Review comment (suggestion):
I'm not sure this should claim that this component is immutable. It's defined as a mutable component, and has public fields, and importantly, the system doesn't notice if the component is replaced at any point (which means that if the mouse-grab key is changed while it is pressed, the mouse would remain grabbed when the key is released).

Code after:
/// A freecam-style camera controller plugin.
///
/// Use the [`FreeCam`] struct to add and customize the controller for a camera entity.
/// The camera's dynamic state is managed by the [`FreeCamState`] struct.
pub struct FreeCamPlugin;

impl Plugin for FreeCamPlugin {
    fn build(&self, app: &mut App) {
        // This ordering is required so that both fixed update and update systems can see the results correctly
        app.add_systems(
            RunFixedMainLoop,
            run_freecam_controller.in_set(RunFixedMainLoopSystems::BeforeFixedMainLoop),
        );
    }
}

/// Scales mouse motion into yaw/pitch movement.
///
/// Based on Valorant's default sensitivity, not entirely sure why it is exactly 1.0 / 180.0,
/// but we're guessing it is a misunderstanding between degrees/radians and then sticking with
/// it because it felt nice.
const RADIANS_PER_DOT: f32 = 1.0 / 180.0;

/// Stores the settings for the [`FreeCam`] controller.
///
/// This component defines static configuration for camera controls,
/// including movement speed, sensitivity, and input bindings.
///
/// From the controller’s perspective, this data is treated as immutable,
/// but it may be modified externally (e.g., by a settings UI) at runtime.
///
/// Add this component to a [`Camera`] entity to enable freecam controls.
/// The associated dynamic state is automatically handled by [`FreeCamState`],
/// which is added to the entity as a required component.
///
/// To activate the controller, add the [`FreeCamPlugin`] to your [`App`].
#[derive(Component)]
#[require(FreeCamState)]
pub struct FreeCam {
    /// Multiplier for pitch and yaw rotation speed.
    pub sensitivity: f32,
    /// [`KeyCode`] for forward translation.
    pub key_forward: KeyCode,
    /// [`KeyCode`] for backward translation.
    pub key_back: KeyCode,
    /// [`KeyCode`] for left translation.
    pub key_left: KeyCode,
    /// [`KeyCode`] for right translation.
    pub key_right: KeyCode,
    /// [`KeyCode`] for up translation.
    pub key_up: KeyCode,