diff --git a/Cargo.toml b/Cargo.toml index 31abee680e030..89e32fecc7672 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -291,6 +291,12 @@ bevy_gizmos = ["bevy_internal/bevy_gizmos"] # Provides a collection of developer tools bevy_dev_tools = ["bevy_internal/bevy_dev_tools"] +# Provides a collection of prebuilt camera controllers +bevy_camera_controller = ["bevy_internal/bevy_camera_controller"] + +# Enables the free cam from bevy_camera_controller +free_cam = ["bevy_internal/free_cam"] + # Enable the Bevy Remote Protocol bevy_remote = ["bevy_internal/bevy_remote"] @@ -1160,6 +1166,7 @@ wasm = true name = "decal" path = "examples/3d/decal.rs" doc-scrape-examples = true +required-features = ["free_cam"] [package.metadata.example.decal] name = "Decal" @@ -1271,6 +1278,7 @@ wasm = true name = "shadow_biases" path = "examples/3d/shadow_biases.rs" doc-scrape-examples = true +required-features = ["free_cam"] [package.metadata.example.shadow_biases] name = "Shadow Biases" @@ -1293,6 +1301,7 @@ wasm = true name = "skybox" path = "examples/3d/skybox.rs" doc-scrape-examples = true +required-features = ["free_cam"] [package.metadata.example.skybox] name = "Skybox" @@ -3196,6 +3205,7 @@ wasm = false name = "scene_viewer" path = "examples/tools/scene_viewer/main.rs" doc-scrape-examples = true +required-features = ["free_cam"] [package.metadata.example.scene_viewer] name = "Scene Viewer" @@ -3971,6 +3981,7 @@ wasm = true name = "3d_gizmos" path = "examples/gizmos/3d_gizmos.rs" doc-scrape-examples = true +required-features = ["free_cam"] [package.metadata.example.3d_gizmos] name = "3D Gizmos" @@ -4603,18 +4614,6 @@ description = "Demonstration of Occlusion Culling" category = "3D Rendering" wasm = false -[[example]] -name = "camera_controller" -path = "examples/helpers/camera_controller.rs" -doc-scrape-examples = true -crate-type = ["lib"] - -[package.metadata.example.camera_controller] -name = "Camera Controller" -description = "Example Free-Cam Styled Camera Controller" -category = "Helpers" -wasm = true - [[example]] name = "widgets" path = "examples/helpers/widgets.rs" diff --git a/crates/bevy_camera_controller/Cargo.toml b/crates/bevy_camera_controller/Cargo.toml new file mode 100644 index 0000000000000..ca024f6ca53ca --- /dev/null +++ b/crates/bevy_camera_controller/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "bevy_camera_controller" +version = "0.18.0-dev" +edition = "2024" +description = "Premade camera controllers for Bevy" +homepage = "https://bevy.org" +repository = "https://github.com/bevyengine/bevy" +license = "MIT OR Apache-2.0" +keywords = ["bevy", "input", "camera", "control"] +rust-version = "1.85.0" + +[dependencies] +bevy_app = { path = "../bevy_app", version = "0.18.0-dev", default-features = false } +bevy_camera = { path = "../bevy_camera", version = "0.18.0-dev", default-features = false } +bevy_ecs = { path = "../bevy_ecs", version = "0.18.0-dev", default-features = false } +bevy_input = { path = "../bevy_input", version = "0.18.0-dev", default-features = false } +bevy_log = { path = "../bevy_log", version = "0.18.0-dev", default-features = false } +bevy_math = { path = "../bevy_math", version = "0.18.0-dev", default-features = false, features = [ + "curve", +] } +bevy_reflect = { path = "../bevy_reflect", version = "0.18.0-dev", default-features = false, optional = true } +bevy_time = { path = "../bevy_time", version = "0.18.0-dev", default-features = false } +bevy_transform = { path = "../bevy_transform", version = "0.18.0-dev", default-features = false } +bevy_window = { path = "../bevy_window", version = "0.18.0-dev", default-features = false } + +[features] +default = ["bevy_reflect"] +bevy_reflect = ["dep:bevy_reflect"] +libm = ["bevy_math/libm"] + +# Camera controllers +free_cam = [] + +[lints] +workspace = true + +[package.metadata.docs.rs] +rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"] +all-features = true diff --git a/crates/bevy_camera_controller/LICENSE-APACHE b/crates/bevy_camera_controller/LICENSE-APACHE new file mode 100644 index 0000000000000..d9a10c0d8e868 --- /dev/null +++ b/crates/bevy_camera_controller/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/crates/bevy_camera_controller/LICENSE-MIT b/crates/bevy_camera_controller/LICENSE-MIT new file mode 100644 index 0000000000000..9cf106272ac3b --- /dev/null +++ b/crates/bevy_camera_controller/LICENSE-MIT @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/helpers/camera_controller.rs b/crates/bevy_camera_controller/src/free_cam.rs similarity index 71% rename from examples/helpers/camera_controller.rs rename to crates/bevy_camera_controller/src/free_cam.rs index 806ca9999a565..998d17bc66566 100644 --- a/examples/helpers/camera_controller.rs +++ b/crates/bevy_camera_controller/src/free_cam.rs @@ -1,37 +1,67 @@ -//! A freecam-style camera controller plugin. -//! To use in your own application: -//! - Copy the code for the [`CameraControllerPlugin`] and add the plugin to your App. -//! - Attach the [`CameraController`] component to an entity with a [`Camera3d`]. +//! A camera controller that allows the user to move freely around the scene. //! -//! Unlike other examples, which demonstrate an application, this demonstrates a plugin library. +//! Free cams are helpful for exploring large scenes, level editors and for debugging. +//! They are rarely useful as-is for gameplay, +//! as they allow the user to move freely in all directions, +//! which can be disorienting, and they can clip through objects and terrain. +//! +//! You may have heard of a "fly cam" before, +//! which are a kind of free cam designed for fluid "flying" movement and quickly surveying large areas. +//! By contrast, the default settings of this particular free cam are optimized for precise control. +//! +//! To use this controller, add [`FreeCamPlugin`] to your app, +//! and [`FreeCam`] to your camera entity. +//! +//! To configure the settings of this controller, modify the fields of the [`FreeCam`] component. -use bevy::{ - input::mouse::{AccumulatedMouseMotion, AccumulatedMouseScroll, MouseScrollUnit}, - prelude::*, - window::{CursorGrabMode, CursorOptions}, +use bevy_app::{App, Plugin, RunFixedMainLoop, RunFixedMainLoopSystems}; +use bevy_camera::Camera; +use bevy_ecs::prelude::*; +use bevy_input::keyboard::KeyCode; +use bevy_input::mouse::{ + AccumulatedMouseMotion, AccumulatedMouseScroll, MouseButton, MouseScrollUnit, }; -use std::{f32::consts::*, fmt}; +use bevy_input::ButtonInput; +use bevy_log::info; +use bevy_math::{EulerRot, Quat, Vec2, Vec3}; +use bevy_time::{Real, Time}; +use bevy_transform::prelude::Transform; +use bevy_window::{CursorGrabMode, CursorOptions, Window}; + +use core::{f32::consts::*, fmt}; /// A freecam-style camera controller plugin. -pub struct CameraControllerPlugin; +/// +/// Use [`FreeCam`] to add a freecam controller to a camera entity, +/// and change its values to customize the controls and change its behavior. +pub struct FreeCamPlugin; -impl Plugin for CameraControllerPlugin { +impl Plugin for FreeCamPlugin { fn build(&self, app: &mut App) { - app.add_systems(Update, run_camera_controller); + // 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 I'm guessing it is a misunderstanding between degrees/radians and then sticking with +/// but we're guessing it is a misunderstanding between degrees/radians and then sticking with /// it because it felt nice. -pub const RADIANS_PER_DOT: f32 = 1.0 / 180.0; +const RADIANS_PER_DOT: f32 = 1.0 / 180.0; -/// Camera controller [`Component`]. +/// Freecam controller settings and state. +/// +/// Add this component to a [`Camera`] entity and add [`FreeCamPlugin`] +/// to your [`App`] to enable freecam controls. #[derive(Component)] -pub struct CameraController { - /// Enables this [`CameraController`] when `true`. +pub struct FreeCam { + /// Enables this [`FreeCam`] when `true`. pub enabled: bool, - /// Indicates if this controller has been initialized by the [`CameraControllerPlugin`]. + /// Indicates if this controller has been initialized by the [`FreeCamPlugin`]. pub initialized: bool, /// Multiplier for pitch and yaw rotation speed. pub sensitivity: f32, @@ -47,8 +77,8 @@ pub struct CameraController { pub key_up: KeyCode, /// [`KeyCode`] for down translation. pub key_down: KeyCode, - /// [`KeyCode`] to use [`run_speed`](CameraController::run_speed) instead of - /// [`walk_speed`](CameraController::walk_speed) for translation. + /// [`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, @@ -58,20 +88,20 @@ pub struct CameraController { pub walk_speed: f32, /// Multiplier for running translation speed. pub run_speed: f32, - /// Multiplier for how the mouse scroll wheel modifies [`walk_speed`](CameraController::walk_speed) - /// and [`run_speed`](CameraController::run_speed). + /// Multiplier for how the mouse scroll wheel modifies [`walk_speed`](FreeCam::walk_speed) + /// and [`run_speed`](FreeCam::run_speed). pub scroll_factor: f32, - /// Friction factor used to exponentially decay [`velocity`](CameraController::velocity) over time. + /// Friction factor used to exponentially decay [`velocity`](FreeCam::velocity) over time. pub friction: f32, - /// This [`CameraController`]'s pitch rotation. + /// This [`FreeCam`]'s pitch rotation. pub pitch: f32, - /// This [`CameraController`]'s yaw rotation. + /// This [`FreeCam`]'s yaw rotation. pub yaw: f32, - /// This [`CameraController`]'s translation velocity. + /// This [`FreeCam`]'s translation velocity. pub velocity: Vec3, } -impl Default for CameraController { +impl Default for FreeCam { fn default() -> Self { Self { enabled: true, @@ -97,7 +127,7 @@ impl Default for CameraController { } } -impl fmt::Display for CameraController { +impl fmt::Display for FreeCam { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, @@ -124,7 +154,11 @@ Freecam Controls: } } -fn run_camera_controller( +/// This system is typically added via the [`FreeCamPlugin`]. +/// +/// Reads inputs and then moves the camera entity according +/// to the settings given in [`FreeCam`]. +pub fn run_freecam_controller( time: Res>, mut windows: Query<(&Window, &mut CursorOptions)>, accumulated_mouse_motion: Res, @@ -133,7 +167,7 @@ fn run_camera_controller( key_input: Res>, mut toggle_cursor_grab: Local, mut mouse_cursor_grab: Local, - mut query: Query<(&mut Transform, &mut CameraController), With>, + mut query: Query<(&mut Transform, &mut FreeCam), With>, ) { let dt = time.delta_secs(); diff --git a/crates/bevy_camera_controller/src/lib.rs b/crates/bevy_camera_controller/src/lib.rs new file mode 100644 index 0000000000000..3626f6c6c59ef --- /dev/null +++ b/crates/bevy_camera_controller/src/lib.rs @@ -0,0 +1,42 @@ +//! A home for first-party camera controllers for Bevy, +//! used for moving the camera around your scene. +//! +//! This crate serves two key purposes: +//! +//! 1. It provides functional camera controllers to help users quickly get started. +//! 2. It holds the camera controllers used by Bevy's own examples and tooling. +//! +//! While these camera controllers are customizable, +//! there is a limit to the customization options available. +//! If you find your project requires different behavior, +//! do not hesitate to copy-paste the camera controller code +//! into your own project and modify it as needed. +//! +//! Each of the provided controllers is gated behind a feature flag, +//! so you don't have to pay for unused camera controllers. +//! These features are all off by default; to enable them, +//! you need to specify the desired features in your Cargo.toml file. +//! +//! For example, to enable the `free_cam` camera controller, +//! you would add the following to your Cargo.toml: +//! +//! ```toml +//! [dependencies] +//! bevy = { version = "0.X", features = ["free_cam"] } +//! ``` +//! +//! Once the correct feature is enabled, +//! add the camera controller plugin to your Bevy app. +//! If your camera is for debugging and development purposes, +//! consider adding a feature flag (e.g. `dev-mode`) or a run condition on +//! the systems in the plugin, which can check a configuration resource. +//! +//! For a full overview of the available camera controllers, +//! please check out the modules of this crate. +//! Each camera controller is stored in its own module, +//! and gated behind a feature flag of the same name. + +#![warn(missing_docs)] + +#[cfg(feature = "free_cam")] +pub mod free_cam; diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 06fec5d262aa7..6bf8630884548 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -307,6 +307,10 @@ meshlet_processor = ["bevy_pbr?/meshlet_processor"] # Provides a collection of developer tools bevy_dev_tools = ["dep:bevy_dev_tools"] +# Provides a collection of prebuilt camera controllers +bevy_camera_controller = ["dep:bevy_camera_controller"] +free_cam = ["bevy_camera_controller/free_cam"] + # Enable support for the Bevy Remote Protocol bevy_remote = ["dep:bevy_remote", "serialize"] @@ -467,6 +471,7 @@ bevy_log = { path = "../bevy_log", version = "0.18.0-dev", optional = true } bevy_a11y = { path = "../bevy_a11y", optional = true, version = "0.18.0-dev", features = [ "bevy_reflect", ] } +bevy_camera_controller = { path = "../bevy_camera_controller", optional = true, version = "0.18.0-dev", default-features = false } bevy_animation = { path = "../bevy_animation", optional = true, version = "0.18.0-dev" } bevy_asset = { path = "../bevy_asset", optional = true, version = "0.18.0-dev" } bevy_audio = { path = "../bevy_audio", optional = true, version = "0.18.0-dev" } diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 09000b7109c75..bc21c32d3467d 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -29,6 +29,8 @@ pub use bevy_asset as asset; pub use bevy_audio as audio; #[cfg(feature = "bevy_camera")] pub use bevy_camera as camera; +#[cfg(feature = "bevy_camera_controller")] +pub use bevy_camera_controller as camera_controller; #[cfg(feature = "bevy_color")] pub use bevy_color as color; #[cfg(feature = "bevy_core_pipeline")] diff --git a/docs/cargo_features.md b/docs/cargo_features.md index e5cd0a08f893e..bc7d105ff42ab 100644 --- a/docs/cargo_features.md +++ b/docs/cargo_features.md @@ -77,6 +77,7 @@ The default feature set enables most of the expected features of a game engine, |async-io|Use async-io's implementation of block_on instead of futures-lite's implementation. This is preferred if your application uses async-io.| |basis-universal|Basis Universal compressed texture support| |bevy_animation|Provides animation functionality| +|bevy_camera_controller|Provides a collection of prebuilt camera controllers| |bevy_ci_testing|Enable systems that allow for automated testing on CI| |bevy_debug_stepping|Enable stepping-based debugging of Bevy systems| |bevy_dev_tools|Provides a collection of developer tools| @@ -102,6 +103,7 @@ The default feature set enables most of the expected features of a game engine, |file_watcher|Enables watching the filesystem for Bevy Asset hot-reloading| |flac|FLAC audio format support| |force_disable_dlss|Forcibly disable DLSS so that cargo build --all-features works without the DLSS SDK being installed. Not meant for users.| +|free_cam|Enables the free cam from bevy_camera_controller| |ghost_nodes|Experimental support for nodes that are ignored for UI layouting| |gif|GIF image format support| |glam_assert|Enable assertions to check the validity of parameters passed to glam| diff --git a/examples/3d/decal.rs b/examples/3d/decal.rs index 3bafcabd1d2ad..3a5328acf6c06 100644 --- a/examples/3d/decal.rs +++ b/examples/3d/decal.rs @@ -1,22 +1,19 @@ //! Decal rendering. //! Note: On Wasm, this example only runs on WebGPU -#[path = "../helpers/camera_controller.rs"] -mod camera_controller; - use bevy::{ anti_alias::fxaa::Fxaa, + camera_controller::free_cam::{FreeCam, FreeCamPlugin}, core_pipeline::prepass::DepthPrepass, pbr::decal::{ForwardDecal, ForwardDecalMaterial, ForwardDecalMaterialExt}, prelude::*, }; -use camera_controller::{CameraController, CameraControllerPlugin}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; fn main() { App::new() - .add_plugins((DefaultPlugins, CameraControllerPlugin)) + .add_plugins((DefaultPlugins, FreeCamPlugin)) .add_systems(Startup, setup) .run(); } @@ -47,7 +44,7 @@ fn setup( commands.spawn(( Name::new("Camera"), Camera3d::default(), - CameraController::default(), + FreeCam::default(), // Must enable the depth prepass to render forward decals DepthPrepass, // Must disable MSAA to use decals on WebGPU diff --git a/examples/3d/meshlet.rs b/examples/3d/meshlet.rs index 74c02b32accf2..1b7ce67c387ca 100644 --- a/examples/3d/meshlet.rs +++ b/examples/3d/meshlet.rs @@ -2,16 +2,13 @@ // Note: This example showcases the meshlet API, but is not the type of scene that would benefit from using meshlets. -#[path = "../helpers/camera_controller.rs"] -mod camera_controller; - use bevy::{ + camera_controller::free_cam::{FreeCam, FreeCamPlugin}, light::{CascadeShadowConfigBuilder, DirectionalLightShadowMap}, pbr::experimental::meshlet::{MeshletMesh3d, MeshletPlugin}, prelude::*, render::render_resource::AsBindGroup, }; -use camera_controller::{CameraController, CameraControllerPlugin}; use std::f32::consts::PI; const ASSET_URL: &str = @@ -26,7 +23,7 @@ fn main() { cluster_buffer_slots: 1 << 14, }, MaterialPlugin::::default(), - CameraControllerPlugin, + FreeCamPlugin, )) .add_systems(Startup, setup) .run(); @@ -49,7 +46,7 @@ fn setup( intensity: 150.0, ..default() }, - CameraController::default(), + FreeCam::default(), )); commands.spawn(( diff --git a/examples/3d/parallax_mapping.rs b/examples/3d/parallax_mapping.rs index 5f5827223e0cf..752b0a7b97fcc 100644 --- a/examples/3d/parallax_mapping.rs +++ b/examples/3d/parallax_mapping.rs @@ -29,7 +29,7 @@ struct Spin { /// The camera, used to move camera on click. #[derive(Component)] -struct CameraController; +struct FreeCamController; const DEPTH_CHANGE_RATE: f32 = 0.1; const DEPTH_UPDATE_STEP: f32 = 0.03; @@ -185,7 +185,7 @@ const CAMERA_POSITIONS: &[Transform] = &[ ]; fn move_camera( - mut camera: Single<&mut Transform, With>, + mut camera: Single<&mut Transform, With>, mut current_view: Local, button: Res>, ) { @@ -217,7 +217,7 @@ fn setup( commands.spawn(( Camera3d::default(), Transform::from_xyz(1.5, 1.5, 1.5).looking_at(Vec3::ZERO, Vec3::Y), - CameraController, + FreeCamController, )); // represent the light source as a sphere diff --git a/examples/3d/shadow_biases.rs b/examples/3d/shadow_biases.rs index 9285aff0adee0..a309b328e4e09 100644 --- a/examples/3d/shadow_biases.rs +++ b/examples/3d/shadow_biases.rs @@ -1,15 +1,15 @@ //! Demonstrates how shadow biases affect shadows in a 3d scene. -#[path = "../helpers/camera_controller.rs"] -mod camera_controller; - -use bevy::{light::ShadowFilteringMethod, prelude::*}; -use camera_controller::{CameraController, CameraControllerPlugin}; +use bevy::{ + camera_controller::free_cam::{FreeCam, FreeCamPlugin}, + light::ShadowFilteringMethod, + prelude::*, +}; fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugins(CameraControllerPlugin) + .add_plugins(FreeCamPlugin) .add_systems(Startup, setup) .add_systems( Update, @@ -68,7 +68,7 @@ fn setup( commands.spawn(( Camera3d::default(), Transform::from_xyz(-1.0, 1.0, 1.0).looking_at(Vec3::new(-1.0, 1.0, 0.0), Vec3::Y), - CameraController::default(), + FreeCam::default(), ShadowFilteringMethod::Hardware2x2, )); diff --git a/examples/3d/skybox.rs b/examples/3d/skybox.rs index 16d167c8d8c33..31fdcc8872262 100644 --- a/examples/3d/skybox.rs +++ b/examples/3d/skybox.rs @@ -1,10 +1,8 @@ //! Load a cubemap texture onto a cube like a skybox and cycle through different compressed texture formats -#[path = "../helpers/camera_controller.rs"] -mod camera_controller; - use bevy::{ anti_alias::taa::TemporalAntiAliasing, + camera_controller::free_cam::{FreeCam, FreeCamPlugin}, core_pipeline::Skybox, image::CompressedImageFormats, pbr::ScreenSpaceAmbientOcclusion, @@ -14,7 +12,6 @@ use bevy::{ renderer::RenderDevice, }, }; -use camera_controller::{CameraController, CameraControllerPlugin}; use std::f32::consts::PI; const CUBEMAPS: &[(&str, CompressedImageFormats)] = &[ @@ -39,7 +36,7 @@ const CUBEMAPS: &[(&str, CompressedImageFormats)] = &[ fn main() { App::new() .add_plugins(DefaultPlugins) - .add_plugins(CameraControllerPlugin) + .add_plugins(FreeCamPlugin) .add_systems(Startup, setup) .add_systems( Update, @@ -77,7 +74,7 @@ fn setup(mut commands: Commands, asset_server: Res) { TemporalAntiAliasing::default(), ScreenSpaceAmbientOcclusion::default(), Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::ZERO, Vec3::Y), - CameraController::default(), + FreeCam::default(), Skybox { image: skybox_handle.clone(), brightness: 1000.0, diff --git a/examples/3d/solari.rs b/examples/3d/solari.rs index b3eaec1112b2d..547bc68dfae81 100644 --- a/examples/3d/solari.rs +++ b/examples/3d/solari.rs @@ -1,11 +1,9 @@ //! Demonstrates realtime dynamic raytraced lighting using Bevy Solari. -#[path = "../helpers/camera_controller.rs"] -mod camera_controller; - use argh::FromArgs; use bevy::{ camera::CameraMainTextureUsages, + camera_controller::free_cam::{FreeCam, FreeCamPlugin}, gltf::GltfMaterialName, prelude::*, render::render_resource::TextureUsages, @@ -15,7 +13,6 @@ use bevy::{ prelude::{RaytracingMesh3d, SolariLighting, SolariPlugins}, }, }; -use camera_controller::{CameraController, CameraControllerPlugin}; use std::f32::consts::PI; #[cfg(all(feature = "dlss", not(feature = "force_disable_dlss")))] @@ -41,7 +38,7 @@ fn main() { "5417916c-0291-4e3f-8f65-326c1858ab96" // Don't copy paste this - generate your own UUID! ))); - app.add_plugins((DefaultPlugins, SolariPlugins, CameraControllerPlugin)) + app.add_plugins((DefaultPlugins, SolariPlugins, FreeCamPlugin)) .insert_resource(args) .add_systems(Startup, setup); @@ -118,7 +115,7 @@ fn setup( clear_color: ClearColorConfig::Custom(Color::BLACK), ..default() }, - CameraController { + FreeCam { walk_speed: 3.0, run_speed: 10.0, ..Default::default() diff --git a/examples/README.md b/examples/README.md index 5ad5ef2fa69c6..349c6adb965f5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -368,7 +368,6 @@ Example | Description Example | Description --- | --- -[Camera Controller](../examples/helpers/camera_controller.rs) | Example Free-Cam Styled Camera Controller [Widgets](../examples/helpers/widgets.rs) | Example UI Widgets ### Input diff --git a/examples/gizmos/3d_gizmos.rs b/examples/gizmos/3d_gizmos.rs index add9551c0355e..a28c571101086 100644 --- a/examples/gizmos/3d_gizmos.rs +++ b/examples/gizmos/3d_gizmos.rs @@ -1,15 +1,15 @@ //! This example demonstrates Bevy's immediate mode drawing API intended for visual debugging. -#[path = "../helpers/camera_controller.rs"] -mod camera_controller; - -use bevy::{color::palettes::css::*, prelude::*}; -use camera_controller::{CameraController, CameraControllerPlugin}; +use bevy::{ + camera_controller::free_cam::{FreeCam, FreeCamPlugin}, + color::palettes::css::*, + prelude::*, +}; use std::f32::consts::PI; fn main() { App::new() - .add_plugins((DefaultPlugins, CameraControllerPlugin)) + .add_plugins((DefaultPlugins, FreeCamPlugin)) .init_gizmo_group::() .add_systems(Startup, setup) .add_systems(Update, (draw_example_collection, update_config)) @@ -52,7 +52,7 @@ fn setup( commands.spawn(( Camera3d::default(), Transform::from_xyz(0., 1.5, 6.).looking_at(Vec3::ZERO, Vec3::Y), - CameraController::default(), + FreeCam::default(), )); // plane commands.spawn(( diff --git a/examples/tools/scene_viewer/main.rs b/examples/tools/scene_viewer/main.rs index 855ad31cc1a21..7c2ba31247d5f 100644 --- a/examples/tools/scene_viewer/main.rs +++ b/examples/tools/scene_viewer/main.rs @@ -12,6 +12,7 @@ use argh::FromArgs; use bevy::{ asset::UnapprovedPathMode, camera::primitives::{Aabb, Sphere}, + camera_controller::free_cam::{FreeCam, FreeCamPlugin}, core_pipeline::prepass::{DeferredPrepass, DepthPrepass}, gltf::GltfPlugin, pbr::DefaultOpaqueRendererMethod, @@ -19,15 +20,11 @@ use bevy::{ render::experimental::occlusion_culling::OcclusionCulling, }; -#[path = "../../helpers/camera_controller.rs"] -mod camera_controller; - #[cfg(feature = "gltf_animation")] mod animation_plugin; mod morph_viewer_plugin; mod scene_viewer_plugin; -use camera_controller::{CameraController, CameraControllerPlugin}; use morph_viewer_plugin::MorphViewerPlugin; use scene_viewer_plugin::{SceneHandle, SceneViewerPlugin}; @@ -98,7 +95,7 @@ fn main() { use_model_forward_direction: args.use_model_forward_direction.unwrap_or(false), ..default() }), - CameraControllerPlugin, + FreeCamPlugin, SceneViewerPlugin, MorphViewerPlugin, )) @@ -178,7 +175,7 @@ fn setup_scene_after_load( projection.far = projection.far.max(size * 10.0); let walk_speed = size * 3.0; - let camera_controller = CameraController { + let camera_controller = FreeCam { walk_speed, run_speed: 3.0 * walk_speed, ..default() diff --git a/examples/tools/scene_viewer/scene_viewer_plugin.rs b/examples/tools/scene_viewer/scene_viewer_plugin.rs index 8a54b0817d121..8ac99bf942c3d 100644 --- a/examples/tools/scene_viewer/scene_viewer_plugin.rs +++ b/examples/tools/scene_viewer/scene_viewer_plugin.rs @@ -4,13 +4,12 @@ //! - Insert an initialized `SceneHandle` resource into your App's `AssetServer`. use bevy::{ - gltf::Gltf, input::common_conditions::input_just_pressed, prelude::*, scene::InstanceId, + camera_controller::free_cam::FreeCam, gltf::Gltf, input::common_conditions::input_just_pressed, + prelude::*, scene::InstanceId, }; use std::{f32::consts::*, fmt}; -use super::camera_controller::*; - #[derive(Resource)] pub struct SceneHandle { pub gltf_handle: Handle, @@ -200,8 +199,8 @@ fn camera_tracker( mut camera_tracker: ResMut, keyboard_input: Res>, mut queries: ParamSet<( - Query<(Entity, &mut Camera), (Added, Without)>, - Query<(Entity, &mut Camera), (Added, With)>, + Query<(Entity, &mut Camera), (Added, Without)>, + Query<(Entity, &mut Camera), (Added, With)>, Query<&mut Camera>, )>, ) { diff --git a/release-content/release-notes/camera_controllers.md b/release-content/release-notes/camera_controllers.md new file mode 100644 index 0000000000000..975886d442bc5 --- /dev/null +++ b/release-content/release-notes/camera_controllers.md @@ -0,0 +1,51 @@ +--- +title: First-party camera controllers +authors: ["@alice-i-cecile"] +pull_requests: [20215] +--- + +To understand a scene, you must look at it through the lens of a camera: explore it, and interact with it. +Because this is such a fundamental operation, game devs have developed a rich collection of tools +called "camera controllers" for manipulating them. + +Getting camera controllers feeling *right* is both tricky and essential: they have a serious +impact on both the feeling of your game and the usability of your software. + +Historically, Bevy has left this entirely up to individual game developers: +camera controllers require deep customization and endless twiddling. +However, Bevy as a game engine needs its *own* camera controllers: +allowing users to quickly and easily explore scenes during development (rather than gameplay). + +To that end, we've created `bevy_camera_controller`: giving us a place to store, share and refine the camera controllers +that we need for easy development, and yes, an eventual Editor. +We're kicking it off with a couple of camera controllers, detailed below. + +### `FreeCam` + +The first camera controller that we've introduced is a "free cam", designed for quickly moving around a scene, +completely ignoring both physics and geometry. +You may have heard of a "flycam" controller before, which is a specialization of a "free cam" controller +designed for fast and fluid movement for covering large amounts of terrain. + +To add a free cam controller to your project (typically under a `dev_mode` feature flag), +add the `FreeCamPlugin` and the `FreeCam` component to your camera entity. + +To configure the settings (speed, behavior, keybindings) or enable / disable the controller modify the `FreeCam` component. +We've done our best to select good defaults, but the details of your scene (especially the scale!) will make a big +difference to what feels right. + +### Using `bevy_camera_controller` in your own projects + +The provided camera controllers are designed to be functional, pleasant debug and dev tools: +add the correct plugin and camera component and you're good to go! + +They can also be useful for prototyping, giving you a quick-and-dirty camera controller +as you get your game off the ground. + +However, they are deliberately *not* architected to give you the level of extensibility and customization +needed to make a production-grade camera controller for games. +Customizatibility comes with a real cost in terms of user experience and maintainability, +and because each project only ever needs one or two distinct camera controllers, exposing more knobs and levers is often a questionable design. +Instead, consider vendoring (read: copy-pasting the source code) the camera controller you want to extend +into your project and rewriting the quite-approachable logic to meet your needs, +or looking for [ecosystem camera crates](https://bevy.org/assets/#camera) that correspond to the genre you're building in.