[
  {
    "path": ".github/workflows/rust.yml",
    "content": "on: [push, pull_request]\n\nname: Continuous integration\n\njobs:\n  update:\n    name: Update\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: dtolnay/rust-toolchain@stable\n      - run: cargo update\n\n  check:\n    name: Check\n    runs-on: ubuntu-latest\n    needs: update\n    steps:\n      - uses: actions/checkout@v3\n      - uses: dtolnay/rust-toolchain@stable\n      - run: cargo check\n\n  test:\n    name: Test Suite\n    runs-on: ubuntu-latest\n    needs: update\n    steps:\n      - uses: actions/checkout@v3\n      - name: Update apt\n        run: sudo apt update\n      - name: Install libudev\n        run: sudo apt install libudev-dev\n      - name: Install libxkbcommon\n        run: sudo apt install libxkbcommon-dev\n      - name: Install libwayland\n        run: sudo apt install libwayland-dev\n      - uses: dtolnay/rust-toolchain@stable\n      - run: cargo test\n\n  fmt:\n    name: Rustfmt\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: dtolnay/rust-toolchain@stable\n      - run: rustup component add rustfmt\n      - run: cargo fmt --all -- --check\n\n  clippy:\n    name: Clippy\n    runs-on: ubuntu-latest\n    needs: update\n    steps:\n      - uses: actions/checkout@v3\n      - uses: dtolnay/rust-toolchain@stable\n      - run: rustup component add clippy\n      - run: cargo clippy -- -D warnings\n"
  },
  {
    "path": ".gitignore",
    "content": "# Generated by Cargo\n# will have compiled files and executables\n/target/\n\n# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries\n# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html\nCargo.lock\n\n# These are backup files generated by rustfmt\n**/*.rs.bk\n\n# IDE\n.idea/\n"
  },
  {
    "path": "61.patch",
    "content": "From a29e882381d84b3a7aa0247ab5e0aad9ff0515da Mon Sep 17 00:00:00 2001\nFrom: \"Mathys R.\" <mat.stjr@gmail.com>\nDate: Wed, 14 Jan 2026 17:29:26 -0500\nSubject: [PATCH 1/3] fix(deprecated): replaced deprecated EventReader to\n MessageReader\n\n---\n examples/scroll.rs | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/examples/scroll.rs b/examples/scroll.rs\nindex 95830a1..8b0049f 100644\n--- a/examples/scroll.rs\n+++ b/examples/scroll.rs\n@@ -82,7 +82,7 @@ fn switch_scroll_type(\n fn scroll(\n     mut settings: ResMut<MovementSettings>,\n     scroll_type: Res<State<ScrollType>>,\n-    mut mouse_wheel_events: EventReader<MouseWheel>,\n+    mut mouse_wheel_events: MessageReader<MouseWheel>,\n     mut query: Query<(&FlyCam, &mut Projection)>,\n ) {\n     for event in mouse_wheel_events.read() {\n\nFrom 70f2e7e1cb08ad6a25c03f0e80af0ea89a262b86 Mon Sep 17 00:00:00 2001\nFrom: \"Mathys R.\" <mat.stjr@gmail.com>\nDate: Wed, 14 Jan 2026 17:29:46 -0500\nSubject: [PATCH 2/3] chore(version): bump version to 0.18.0\n\n---\n Cargo.toml | 6 +++---\n README.md  | 1 +\n 2 files changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/Cargo.toml b/Cargo.toml\nindex 93db9a0..041041c 100644\n--- a/Cargo.toml\n+++ b/Cargo.toml\n@@ -1,6 +1,6 @@\n [package]\n name = \"bevy_flycam\"\n-version = \"0.17.0\"\n+version = \"0.18.0\"\n authors = [\"Spencer Burris <sburris@posteo.net>\"]\n edition = \"2021\"\n license = \"ISC\"\n@@ -13,7 +13,7 @@ categories = [\"game-engines\", \"game-development\"]\n \n # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n [dependencies]\n-bevy = { version = \"0.17\", default-features = false, features = [\n+bevy = { version = \"0.18\", default-features = false, features = [\n     \"std\",\n     \"bevy_asset\",\n     \"bevy_core_pipeline\",\n@@ -23,7 +23,7 @@ bevy = { version = \"0.17\", default-features = false, features = [\n ] }\n \n [dev-dependencies]\n-bevy = { version = \"0.17\", default-features = false, features = [\n+bevy = { version = \"0.18\", default-features = false, features = [\n     \"bevy_asset\",\n     \"bevy_core_pipeline\",\n     \"bevy_pbr\",\ndiff --git a/README.md b/README.md\nindex 9896e7a..2e00f2f 100644\n--- a/README.md\n+++ b/README.md\n@@ -123,6 +123,7 @@ fn setup(mut commands: Commands) {\n bevy_flycam's crate version follows bevy's minor version as shown:\n | bevy     | bevy_flycam |\n | :--      | :--         |\n+| `0.18.0` | `0.18.0`    |\n | `0.17.2` | `0.17.2`    |\n | `0.16.1` | `0.16.1`    |\n | `0.15.0` | `0.15.0`    |\n\nFrom 29c24d2d115d0720e5840b1033947f55ae2f7fb4 Mon Sep 17 00:00:00 2001\nFrom: \"Mathys R.\" <mat.stjr@gmail.com>\nDate: Wed, 14 Jan 2026 17:30:16 -0500\nSubject: [PATCH 3/3] refactor: creating a common_build function to avoid\n duplicate code.\n\n---\n src/lib.rs | 28 ++++++++++++++--------------\n 1 file changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/src/lib.rs b/src/lib.rs\nindex ce44401..700e358 100644\n--- a/src/lib.rs\n+++ b/src/lib.rs\n@@ -184,13 +184,8 @@ fn initial_grab_on_flycam_spawn(\n pub struct PlayerPlugin;\n impl Plugin for PlayerPlugin {\n     fn build(&self, app: &mut App) {\n-        app.init_resource::<MovementSettings>()\n-            .init_resource::<KeyBindings>()\n-            .add_systems(Startup, setup_player)\n-            .add_systems(Startup, initial_grab_cursor)\n-            .add_systems(Update, player_move)\n-            .add_systems(Update, player_look)\n-            .add_systems(Update, cursor_grab);\n+        common_build(app);\n+        app.add_systems(Startup, setup_player);\n     }\n }\n \n@@ -198,12 +193,17 @@ impl Plugin for PlayerPlugin {\n pub struct NoCameraPlayerPlugin;\n impl Plugin for NoCameraPlayerPlugin {\n     fn build(&self, app: &mut App) {\n-        app.init_resource::<MovementSettings>()\n-            .init_resource::<KeyBindings>()\n-            .add_systems(Startup, initial_grab_cursor)\n-            .add_systems(Startup, initial_grab_on_flycam_spawn)\n-            .add_systems(Update, player_move)\n-            .add_systems(Update, player_look)\n-            .add_systems(Update, cursor_grab);\n+        common_build(app);\n     }\n }\n+\n+/// Common build steps for both PlayerPlugin and NoCameraPlayerPlugin\n+fn common_build(app: &mut App) {\n+    app.init_resource::<MovementSettings>()\n+        .init_resource::<KeyBindings>()\n+        .add_systems(Startup, initial_grab_cursor)\n+        .add_systems(Startup, initial_grab_on_flycam_spawn)\n+        .add_systems(Update, player_move)\n+        .add_systems(Update, player_look)\n+        .add_systems(Update, cursor_grab);\n+}\n\\ No newline at end of file\n\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"bevy_flycam\"\nversion = \"0.18.0\"\nauthors = [\"Spencer Burris <sburris@posteo.net>\"]\nedition = \"2021\"\nlicense = \"ISC\"\ndescription = \"Basic first-person fly camera for the Bevy game engine\"\nhomepage = \"https://github.com/sburris0/bevy_flycam/\"\nrepository = \"https://github.com/sburris0/bevy_flycam/\"\nreadme = \"README.md\"\nkeywords = [\"gamedev\", \"bevy\", \"3d\", \"camera\"]\ncategories = [\"game-engines\", \"game-development\"]\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n[dependencies]\nbevy = { version = \"0.18\", default-features = false, features = [\n    \"std\",\n    \"bevy_asset\",\n    \"bevy_core_pipeline\",\n    \"bevy_render\",\n    \"bevy_log\",\n    \"bevy_window\",\n] }\n\n[dev-dependencies]\nbevy = { version = \"0.18\", default-features = false, features = [\n    \"bevy_asset\",\n    \"bevy_core_pipeline\",\n    \"bevy_pbr\",\n    \"bevy_state\",\n    \"bevy_window\",\n    \"ktx2\",\n    \"tonemapping_luts\",\n    \"wayland\",\n    \"x11\",\n    \"zstd_rust\",\n] }\n"
  },
  {
    "path": "LICENSE.md",
    "content": "Copyright 2020 Spencer Burris\n\nPermission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# bevy_flycam\n\n[![Crates.io](https://img.shields.io/crates/v/bevy_flycam)](https://crates.io/crates/bevy_flycam)\n![Crates.io](https://img.shields.io/crates/l/bevy_flycam)\n![docs.rs](https://img.shields.io/docsrs/bevy_flycam)\n\nA basic first-person fly camera for Bevy\n\n## Controls\n\n- WASD to move horizontally\n- SPACE to ascend\n- LSHIFT to descend\n- ESC to grab/release cursor.\n\n## Comparison\n\nThere are a few notable differences from [bevy_fly_camera](https://github.com/mcpar-land/bevy_fly_camera)...\n\n- No linear interpolation\n- Cursor grabbing\n- Shorter code\n- Single-line setup\n- A tiny bit faster?\n\n## Usage\n\n1. Add to `Cargo.toml` or copy `lib.rs` to your own file\n\n    ```toml\n    [dependencies]\n    bevy = \"0.18\"\n    bevy_flycam = \"*\"\n    ```\n\n    or\n\n    ```toml\n    [dependencies]\n    bevy = \"0.18\"\n    bevy_flycam = { git = \"https://github.com/sburris0/bevy_flycam\" }\n    ```\n\n2. Include the prelude:\n\n    ```rust\n    use bevy_flycam::prelude::*;\n    ```\n\n3. Add the `PlayerPlugin`:\n\n    ```rust\n    #[bevy_main]\n    fn main() {\n        App::new()\n            .add_plugins(DefaultPlugins)\n            .add_plugins(PlayerPlugin)\n            .run();\n    }\n    ```\n\nNote that `PlayerPlugin` will spawn a camera for you. See [Using your own camera](#using-your-own-camera) for details on how to\nuse a pre-existing one.\n\nAlternatively you can see the example `basic.rs` or `scroll.rs` located in the examples folder.\nYou can run the example by cloning this repository and run the command: `cargo run --release --example basic`\n\n## Customization\n\n### Movement and keybindings\n\nTo modify player movement speed or mouse sensitivity add it as a resource. </br>\nSame thing goes for the keybindings used for moving the camera.\n\n```Rust\n#[bevy_main]\nfn main() {\n    App::new()\n        .add_plugins(DefaultPlugins)\n        .add_plugins(PlayerPlugin)\n        .insert_resource(MovementSettings {\n            sensitivity: 0.00015, // default: 0.00012\n            speed: 12.0, // default: 12.0\n        })\n        .insert_resource(KeyBindings {\n            move_ascend: KeyCode::E,\n            move_descend: KeyCode::Q,\n            ..Default::default()\n        })\n        .run();\n}\n```\n\n### Using your own camera\n\nYou can also use `NoCameraPlayerPlugin` if you want to use your own camera. Be sure to add the `FlyCam` component to your own camera or else this plugin won't know what to move.\n\n```Rust\n#[bevy_main]\nfn main() {\n    App::new()\n        .add_plugins(DefaultPlugins)\n        .add_plugins(NoCameraPlayerPlugin)\n        .add_systems(Startup, setup)\n        .run();\n}\n\nfn setup(mut commands: Commands) {\n    commands.spawn((\n        Camera3dBundle {\n            transform: Transform::from_xyz(0.0, 2.0, 0.5),\n            ..default()\n        },\n        FlyCam\n    ));\n}\n```\n\n## Support\n\n[![Bevy tracking](https://img.shields.io/badge/Bevy%20tracking-released%20version-lightblue)](https://github.com/bevyengine/bevy/blob/main/docs/plugins_guidelines.md#main-branch-tracking)\n\nbevy_flycam's crate version follows bevy's minor version as shown:\n| bevy     | bevy_flycam |\n| :--      | :--         |\n| `0.18.0` | `0.18.0`    |\n| `0.17.2` | `0.17.2`    |\n| `0.16.1` | `0.16.1`    |\n| `0.15.0` | `0.15.0`    |\n| `0.14.0` | `0.14.0`    |\n| `0.13.0` | `0.13.0`    |\n| `0.12.0` | `0.12.0`    |\n| `0.11.0` | `0.11.0`    |\n| `0.10.1` | `0.10.1`    |\n\n## Contributing\n\nPRs are very welcome.\n"
  },
  {
    "path": "examples/basic.rs",
    "content": "use bevy::prelude::*;\nuse bevy_flycam::prelude::*;\n\n//From bevy examples:\n//https://github.com/bevyengine/bevy/blob/latest/examples/3d/3d_scene.rs\n\nfn main() {\n    App::new()\n        .add_plugins(DefaultPlugins)\n        .add_plugins(PlayerPlugin)\n        .insert_resource(MovementSettings {\n            sensitivity: 0.00015, // default: 0.00012\n            speed: 12.0,          // default: 12.0\n        })\n        .add_systems(Startup, setup)\n        .run();\n}\n\n/// set up a simple 3D scene\nfn setup(\n    mut commands: Commands,\n    mut meshes: ResMut<Assets<Mesh>>,\n    mut materials: ResMut<Assets<StandardMaterial>>,\n) {\n    // plane\n    commands.spawn((\n        Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(2.5)))),\n        MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),\n    ));\n\n    // cube\n    commands.spawn((\n        Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),\n        MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))),\n        Transform::from_xyz(0.0, 0.5, 0.0),\n    ));\n\n    // light\n    commands.spawn((PointLight::default(), Transform::from_xyz(4.0, 8.0, 4.0)));\n\n    info!(\"Move camera around by using WASD for lateral movement\");\n    info!(\"Use Left Shift and Spacebar for vertical movement\");\n    info!(\"Use the mouse to look around\");\n    info!(\"Press Esc to hide or show the mouse cursor\");\n}\n"
  },
  {
    "path": "examples/key_bindings.rs",
    "content": "use bevy::prelude::*;\nuse bevy_flycam::prelude::*;\n\n//From bevy examples:\n//https://github.com/bevyengine/bevy/blob/latest/examples/3d/3d_scene.rs\n\nfn main() {\n    App::new()\n        .add_plugins(DefaultPlugins)\n        .add_plugins(PlayerPlugin)\n        .insert_resource(MovementSettings {\n            sensitivity: 0.00015, // default: 0.00012\n            speed: 12.0,          // default: 12.0\n        })\n        // Unreal movement layout\n        .insert_resource(KeyBindings {\n            move_ascend: KeyCode::KeyE,\n            move_descend: KeyCode::KeyQ,\n            ..Default::default()\n        })\n        .add_systems(Startup, setup)\n        .run();\n}\n\n/// set up a simple 3D scene\nfn setup(\n    mut commands: Commands,\n    mut meshes: ResMut<Assets<Mesh>>,\n    mut materials: ResMut<Assets<StandardMaterial>>,\n) {\n    // plane\n    commands.spawn((\n        Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(2.5)))),\n        MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),\n    ));\n\n    // cube\n    commands.spawn((\n        Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),\n        MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))),\n        Transform::from_xyz(0.0, 0.5, 0.0),\n    ));\n\n    // light\n    commands.spawn((PointLight::default(), Transform::from_xyz(4.0, 8.0, 4.0)));\n}\n"
  },
  {
    "path": "examples/scroll.rs",
    "content": "use bevy::{input::mouse::MouseWheel, prelude::*};\nuse bevy_flycam::prelude::*;\n\n// From bevy examples:\n// https://github.com/bevyengine/bevy/blob/latest/examples/3d/3d_scene.rs\n\n#[derive(States, Default, Clone, Eq, PartialEq, Debug, Hash)]\nenum ScrollType {\n    #[default]\n    MovementSpeed,\n    Zoom,\n}\n\nfn main() {\n    App::new()\n        .add_plugins(DefaultPlugins)\n        //NoCameraPlayerPlugin as we provide the camera\n        .add_plugins(NoCameraPlayerPlugin)\n        .insert_resource(MovementSettings {\n            ..Default::default()\n        })\n        // Setting initial state\n        .init_state::<ScrollType>()\n        .add_systems(Startup, setup)\n        .add_systems(Update, switch_scroll_type)\n        .add_systems(Update, scroll)\n        .run();\n}\n\n/// set up a simple 3D scene\nfn setup(\n    mut commands: Commands,\n    mut meshes: ResMut<Assets<Mesh>>,\n    mut materials: ResMut<Assets<StandardMaterial>>,\n) {\n    // plane\n    commands.spawn((\n        Mesh3d(meshes.add(Plane3d::new(Vec3::Y, Vec2::splat(2.5)))),\n        MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),\n    ));\n\n    // cube\n    commands.spawn((\n        Mesh3d(meshes.add(Cuboid::new(1.0, 1.0, 1.0))),\n        MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))),\n        Transform::from_xyz(0.0, 0.5, 0.0),\n    ));\n\n    // light\n    // light\n    commands.spawn((PointLight::default(), Transform::from_xyz(4.0, 8.0, 4.0)));\n\n    // add camera\n    commands.spawn((\n        Camera3d::default(),\n        Transform::from_xyz(-2.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),\n        FlyCam,\n    ));\n\n    info!(\"Press 'Z' to switch between Movement Speed and Zoom\");\n    info!(\"Changing the selected value by scrolling the mousewheel\");\n}\n\n/// Listens for Z key being pressed and toggles between the two scroll-type states [`ScrollType`]\nfn switch_scroll_type(\n    scroll_type: Res<State<ScrollType>>,\n    mut next_scroll_type: ResMut<NextState<ScrollType>>,\n    keyboard_input: Res<ButtonInput<KeyCode>>,\n) {\n    if keyboard_input.just_pressed(KeyCode::KeyZ) {\n        let result = match scroll_type.get() {\n            ScrollType::MovementSpeed => ScrollType::Zoom,\n            ScrollType::Zoom => ScrollType::MovementSpeed,\n        };\n\n        println!(\"{:?}\", result);\n        next_scroll_type.set(result);\n    }\n}\n\n/// Depending on the state, the mouse-scroll changes either the movement speed or the field-of-view of the camera\nfn scroll(\n    mut settings: ResMut<MovementSettings>,\n    scroll_type: Res<State<ScrollType>>,\n    mut mouse_wheel_events: MessageReader<MouseWheel>,\n    mut query: Query<(&FlyCam, &mut Projection)>,\n) {\n    for event in mouse_wheel_events.read() {\n        if *scroll_type.get() == ScrollType::MovementSpeed {\n            settings.speed = (settings.speed + event.y * 0.1).abs();\n            println!(\"Speed: {:?}\", settings.speed);\n        } else {\n            for (_camera, project) in query.iter_mut() {\n                if let Projection::Perspective(perspective) = project.into_inner() {\n                    perspective.fov = (perspective.fov - event.y * 0.01).abs();\n                    println!(\"FOV: {:?}\", perspective.fov);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "shell.nix",
    "content": "{ pkgs ? import <nixpkgs> { } }:\n\nwith pkgs;\n\nmkShell rec {\n  nativeBuildInputs = [\n    pkg-config\n  ];\n  buildInputs = [\n    udev alsa-lib vulkan-loader\n    xorg.libX11 xorg.libXcursor xorg.libXi xorg.libXrandr # To use the x11 feature\n    libxkbcommon wayland # To use the wayland feature\n  ];\n  LD_LIBRARY_PATH = lib.makeLibraryPath buildInputs;\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "use bevy::input::mouse::MouseMotion;\nuse bevy::prelude::*;\nuse bevy::window::{CursorGrabMode, CursorOptions, PrimaryWindow};\n\npub mod prelude {\n    pub use crate::*;\n}\n\n/// Mouse sensitivity and movement speed\n#[derive(Resource)]\npub struct MovementSettings {\n    pub sensitivity: f32,\n    pub speed: f32,\n}\n\nimpl Default for MovementSettings {\n    fn default() -> Self {\n        Self {\n            sensitivity: 0.00012,\n            speed: 12.,\n        }\n    }\n}\n\n/// Key configuration\n#[derive(Resource)]\npub struct KeyBindings {\n    pub move_forward: KeyCode,\n    pub move_backward: KeyCode,\n    pub move_left: KeyCode,\n    pub move_right: KeyCode,\n    pub move_ascend: KeyCode,\n    pub move_descend: KeyCode,\n    pub toggle_grab_cursor: KeyCode,\n}\n\nimpl Default for KeyBindings {\n    fn default() -> Self {\n        Self {\n            move_forward: KeyCode::KeyW,\n            move_backward: KeyCode::KeyS,\n            move_left: KeyCode::KeyA,\n            move_right: KeyCode::KeyD,\n            move_ascend: KeyCode::Space,\n            move_descend: KeyCode::ShiftLeft,\n            toggle_grab_cursor: KeyCode::Escape,\n        }\n    }\n}\n\n/// Used in queries when you want flycams and not other cameras\n/// A marker component used in queries when you want flycams and not other cameras\n#[derive(Component)]\npub struct FlyCam;\n\n/// Grabs/ungrabs mouse cursor\nfn toggle_grab_cursor(mut primary_cursor_options: Single<&mut CursorOptions, With<PrimaryWindow>>) {\n    match primary_cursor_options.grab_mode {\n        CursorGrabMode::None => {\n            primary_cursor_options.grab_mode = CursorGrabMode::Confined;\n            primary_cursor_options.visible = false;\n        }\n        _ => {\n            primary_cursor_options.grab_mode = CursorGrabMode::None;\n            primary_cursor_options.visible = true;\n        }\n    }\n}\n\n/// Grabs the cursor when game first starts\nfn initial_grab_cursor(primary_cursor_options: Single<&mut CursorOptions, With<PrimaryWindow>>) {\n    toggle_grab_cursor(primary_cursor_options);\n}\n\n/// Spawns the `Camera3dBundle` to be controlled\nfn setup_player(mut commands: Commands) {\n    commands.spawn((\n        Camera3d::default(),\n        FlyCam,\n        Transform::from_xyz(-2.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),\n    ));\n}\n\n/// Handles keyboard input and movement\nfn player_move(\n    keys: Res<ButtonInput<KeyCode>>,\n    time: Res<Time>,\n    primary_cursor_options: Single<&mut CursorOptions, With<PrimaryWindow>>,\n    settings: Res<MovementSettings>,\n    key_bindings: Res<KeyBindings>,\n    mut query: Query<(&FlyCam, &mut Transform)>, //    mut query: Query<&mut Transform, With<FlyCam>>,\n) {\n    for (_camera, mut transform) in query.iter_mut() {\n        let mut velocity = Vec3::ZERO;\n        let local_z = transform.local_z();\n        let forward = -Vec3::new(local_z.x, 0., local_z.z);\n        let right = Vec3::new(local_z.z, 0., -local_z.x);\n\n        for key in keys.get_pressed() {\n            match primary_cursor_options.grab_mode {\n                CursorGrabMode::None => (),\n                _ => {\n                    let key = *key;\n                    if key == key_bindings.move_forward {\n                        velocity += forward;\n                    } else if key == key_bindings.move_backward {\n                        velocity -= forward;\n                    } else if key == key_bindings.move_left {\n                        velocity -= right;\n                    } else if key == key_bindings.move_right {\n                        velocity += right;\n                    } else if key == key_bindings.move_ascend {\n                        velocity += Vec3::Y;\n                    } else if key == key_bindings.move_descend {\n                        velocity -= Vec3::Y;\n                    }\n                }\n            }\n        }\n\n        velocity = velocity.normalize_or_zero();\n\n        transform.translation += velocity * time.delta_secs() * settings.speed\n    }\n}\n\n/// Handles looking around if cursor is locked\nfn player_look(\n    settings: Res<MovementSettings>,\n    primary_window: Query<&mut Window, With<PrimaryWindow>>,\n    primary_cursor_options: Single<&mut CursorOptions, With<PrimaryWindow>>,\n    mut state: MessageReader<MouseMotion>,\n    mut query: Query<&mut Transform, With<FlyCam>>,\n) {\n    if let Ok(window) = primary_window.single() {\n        for mut transform in query.iter_mut() {\n            for ev in state.read() {\n                let (mut yaw, mut pitch, _) = transform.rotation.to_euler(EulerRot::YXZ);\n                match primary_cursor_options.grab_mode {\n                    CursorGrabMode::None => (),\n                    _ => {\n                        // Using smallest of height or width ensures equal vertical and horizontal sensitivity\n                        let window_scale = window.height().min(window.width());\n                        pitch -= (settings.sensitivity * ev.delta.y * window_scale).to_radians();\n                        yaw -= (settings.sensitivity * ev.delta.x * window_scale).to_radians();\n                    }\n                }\n\n                pitch = pitch.clamp(-1.54, 1.54);\n\n                // Order is important to prevent unintended roll\n                transform.rotation =\n                    Quat::from_axis_angle(Vec3::Y, yaw) * Quat::from_axis_angle(Vec3::X, pitch);\n            }\n        }\n    } else {\n        warn!(\"Primary window not found for `player_look`!\");\n    }\n}\n\nfn cursor_grab(\n    keys: Res<ButtonInput<KeyCode>>,\n    key_bindings: Res<KeyBindings>,\n    primary_cursor_options: Single<&mut CursorOptions, With<PrimaryWindow>>,\n) {\n    if keys.just_pressed(key_bindings.toggle_grab_cursor) {\n        toggle_grab_cursor(primary_cursor_options);\n    }\n}\n\n// Grab cursor when an entity with FlyCam is added\nfn initial_grab_on_flycam_spawn(\n    query_added: Query<Entity, Added<FlyCam>>,\n    primary_cursor_options: Single<&mut CursorOptions, With<PrimaryWindow>>,\n) {\n    if query_added.is_empty() {\n        return;\n    }\n\n    toggle_grab_cursor(primary_cursor_options);\n}\n\n/// Contains everything needed to add first-person fly camera behavior to your game\npub struct PlayerPlugin;\nimpl Plugin for PlayerPlugin {\n    fn build(&self, app: &mut App) {\n        common_build(app);\n        app.add_systems(Startup, setup_player);\n    }\n}\n\n/// Same as [`PlayerPlugin`] but does not spawn a camera\npub struct NoCameraPlayerPlugin;\nimpl Plugin for NoCameraPlayerPlugin {\n    fn build(&self, app: &mut App) {\n        common_build(app);\n    }\n}\n\n/// Common build steps for both PlayerPlugin and NoCameraPlayerPlugin\nfn common_build(app: &mut App) {\n    app.init_resource::<MovementSettings>()\n        .init_resource::<KeyBindings>()\n        .add_systems(Startup, initial_grab_cursor)\n        .add_systems(Startup, initial_grab_on_flycam_spawn)\n        .add_systems(Update, player_move)\n        .add_systems(Update, player_look)\n        .add_systems(Update, cursor_grab);\n}\n"
  }
]