├── .gitignore ├── examples ├── 2d_scene.rs └── 3d_scene.rs ├── Cargo.toml ├── LICENSE-MIT ├── .github └── workflows │ └── main.yml ├── README.md ├── src └── lib.rs └── LICENSE-APACHE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /examples/2d_scene.rs: -------------------------------------------------------------------------------- 1 | //! A simple 2D scene using orthographic controls. 2 | 3 | use bevy::prelude::*; 4 | use bevy_spectator::*; 5 | 6 | fn main() { 7 | App::new() 8 | .insert_resource(SpectatorSettings { 9 | base_speed: 100.0, 10 | alt_speed: 350.0, 11 | orthographic: true, 12 | ..default() 13 | }) 14 | .add_plugins((DefaultPlugins, SpectatorPlugin)) 15 | .add_systems(Startup, setup) 16 | .run(); 17 | } 18 | 19 | fn setup(mut commands: Commands) { 20 | commands.spawn((Camera2d, Spectator)); 21 | commands.spawn(( 22 | Sprite::default(), 23 | Transform::from_scale(Vec3::new(10.0, 10.0, 1.0)), 24 | )); 25 | } 26 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_spectator" 3 | description = "A spectator camera plugin for Bevy" 4 | version = "0.8.0" 5 | edition = "2021" 6 | authors = ["JonahPlusPlus <33059163+JonahPlusPlus@users.noreply.github.com>"] 7 | license = "MIT OR Apache-2.0" 8 | documentation = "https://docs.rs/bevy_spectator" 9 | homepage = "https://github.com/JonahPlusPlus/bevy_spectator" 10 | repository = "https://github.com/JonahPlusPlus/bevy_spectator" 11 | include = ["/src", "/examples", "/LICENSE*"] 12 | 13 | [dependencies] 14 | bevy = { version = "0.16", default-features = false, features = [ 15 | "bevy_window", 16 | "bevy_log", 17 | ] } 18 | 19 | [dev-dependencies] 20 | bevy = { version = "0.16", default-features = false, features = [ 21 | "bevy_asset", 22 | "bevy_core_pipeline", 23 | "bevy_pbr", 24 | "bevy_render", 25 | "bevy_sprite", 26 | "bevy_window", 27 | "bevy_log", 28 | "x11", 29 | "ktx2", 30 | "zstd", 31 | "tonemapping_luts", 32 | ] } 33 | 34 | [features] 35 | default = ["init"] 36 | init = [] # Enables automatically choosing a camera 37 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 JonahPlusPlus (Jonah Henriksson) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /examples/3d_scene.rs: -------------------------------------------------------------------------------- 1 | //! A simple scene (based off Bevy's 3d_scene example). 2 | 3 | use bevy::prelude::*; 4 | use bevy_spectator::*; 5 | 6 | fn main() { 7 | App::new() 8 | .insert_resource(SpectatorSettings { 9 | base_speed: 5.0, 10 | alt_speed: 15.0, 11 | sensitivity: 0.0015, 12 | ..default() 13 | }) 14 | .add_plugins((DefaultPlugins, SpectatorPlugin)) 15 | .add_systems(Startup, setup) 16 | .run(); 17 | } 18 | 19 | fn setup( 20 | mut commands: Commands, 21 | mut meshes: ResMut>, 22 | mut materials: ResMut>, 23 | ) { 24 | commands.spawn(( 25 | Camera3d::default(), 26 | Transform::from_xyz(-3.0, 1.5, 3.0).looking_at(Vec3::ZERO, Vec3::Y), 27 | Spectator, 28 | )); 29 | commands.spawn(( 30 | PointLight { 31 | shadows_enabled: true, 32 | ..default() 33 | }, 34 | Transform::from_xyz(4.0, 8.0, 4.0), 35 | )); 36 | 37 | commands.spawn(( 38 | Mesh3d(meshes.add(Plane3d::default().mesh().size(5.0, 5.0))), 39 | MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))), 40 | )); 41 | commands.spawn(( 42 | Mesh3d(meshes.add(Cuboid::default())), 43 | MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))), 44 | Transform::from_xyz(0.0, 0.5, 0.0), 45 | )); 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | on: 3 | push: 4 | branches: 5 | - master 6 | paths-ignore: 7 | - ".gitignore" 8 | pull_request: 9 | paths-ignore: 10 | - ".gitignore" 11 | env: 12 | CARGO_TERM_COLOR: always 13 | jobs: 14 | format: 15 | name: Format 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Clone repo 19 | uses: actions/checkout@v3 20 | 21 | - name: Cache crates 22 | uses: Swatinem/rust-cache@v2 23 | 24 | - name: Install Taplo 25 | run: cargo install taplo-cli 26 | 27 | - name: Format 28 | run: | 29 | cargo fmt --check 30 | taplo fmt --check 31 | 32 | lint: 33 | name: Lint 34 | runs-on: ubuntu-latest 35 | steps: 36 | - name: Clone repo 37 | uses: actions/checkout@v3 38 | 39 | - name: Instal stable toolchain 40 | uses: dtolnay/rust-toolchain@stable 41 | 42 | - name: Cache crates 43 | uses: Swatinem/rust-cache@v2 44 | 45 | - name: Clippy 46 | run: cargo clippy --no-deps --tests --examples -- -D warnings 47 | 48 | - name: Rustdoc 49 | run: cargo rustdoc -- -D warnings 50 | 51 | test: 52 | name: Test 53 | runs-on: ubuntu-latest 54 | steps: 55 | - name: Clone repo 56 | uses: actions/checkout@v3 57 | 58 | - name: Instal stable toolchain 59 | uses: dtolnay/rust-toolchain@stable 60 | 61 | - name: Cache crates 62 | uses: Swatinem/rust-cache@v2 63 | 64 | - name: Test 65 | run: cargo test 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bevy Spectator 2 | 3 | [![crates.io](https://img.shields.io/crates/v/bevy_spectator)](https://crates.io/crates/bevy_spectator) 4 | [![crates.io](https://img.shields.io/crates/d/bevy_spectator)](https://crates.io/crates/bevy_spectator) 5 | [![docs.rs](https://docs.rs/bevy_spectator/badge.svg)](https://docs.rs/bevy_spectator) 6 | 7 | A spectator camera plugin for the [Bevy game engine](https://bevyengine.org/). 8 | 9 | ## Controls 10 | 11 | | Action | Key | 12 | |-------------------|---------------| 13 | | Forward | `W` | 14 | | Left | `A` | 15 | | Backward | `S` | 16 | | Right | `D` | 17 | | Up | `Space` | 18 | | Down | `ControlLeft` | 19 | | Alternative Speed | `ShiftLeft` | 20 | | Release Cursor | `Escape` | 21 | 22 | Movement is constrained to the appropriate axes. (`WASD` to X & Z axes, `Space` & `ShiftLeft` to the Y axis) 23 | 24 | When in orthographic mode, only `WASD` is used. 25 | 26 | ## Basic example 27 | 28 | ```rust,no_run 29 | use bevy::prelude::*; 30 | use bevy_spectator::*; 31 | 32 | fn main() { 33 | App::new() 34 | .add_plugins((DefaultPlugins, SpectatorPlugin)) 35 | .add_systems(Startup, setup) 36 | .run(); 37 | } 38 | 39 | fn setup(mut commands: Commands) { 40 | commands.spawn(( 41 | Camera3d::default(), Spectator 42 | )); 43 | } 44 | ``` 45 | 46 | ## Bevy compatibility 47 | 48 | | bevy | bevy_spectator | 49 | |------|----------------| 50 | | 0.16 | 0.8 | 51 | | 0.15 | 0.7 | 52 | | 0.14 | 0.6 | 53 | | 0.13 | 0.5 | 54 | | 0.12 | 0.4 | 55 | | 0.11 | 0.3 | 56 | | 0.10 | 0.2 | 57 | | 0.9 | 0.1 | 58 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use bevy::{ 4 | input::mouse::MouseMotion, 5 | log::{error, warn}, 6 | prelude::*, 7 | window::{CursorGrabMode, PrimaryWindow}, 8 | }; 9 | 10 | /// A marker `Component` for spectating cameras. 11 | /// 12 | /// ## Usage 13 | /// With the `init` feature: 14 | /// - Add it to a single entity to mark it as a spectator. 15 | /// - `init` will then find that entity and mark it as the active spectator in [`SpectatorSettings`]. 16 | /// 17 | /// (If there isn't a single [`Spectator`] (none or multiple, instead of one), there won't be an active spectator selected by the `init` feature.) 18 | /// 19 | /// Without the `init` feature: 20 | /// - Add it to entities to mark spectators. 21 | /// - Manually alter [`SpectatorSettings`] to set the active spectator. 22 | #[derive(Component)] 23 | pub struct Spectator; 24 | 25 | /// A `Plugin` for spectating your scene. 26 | pub struct SpectatorPlugin; 27 | 28 | impl Plugin for SpectatorPlugin { 29 | fn build(&self, app: &mut App) { 30 | app.init_resource::(); 31 | 32 | #[cfg(feature = "init")] 33 | app.add_systems(PostStartup, spectator_init); 34 | 35 | app.add_systems(Update, spectator_update); 36 | } 37 | } 38 | 39 | #[cfg(feature = "init")] 40 | fn spectator_init( 41 | cameras: Query>, 42 | mut settings: ResMut, 43 | ) { 44 | use bevy::ecs::query::QuerySingleError; 45 | 46 | if settings.active_spectator.is_none() { 47 | settings.active_spectator = match cameras.single() { 48 | Ok(a) => Some(a), 49 | Err(QuerySingleError::NoEntities(_)) => { 50 | warn!("Failed to find a Spectator; Active camera will remain unset."); 51 | None 52 | } 53 | Err(QuerySingleError::MultipleEntities(_)) => { 54 | warn!("Found more than one Spectator; Active camera will remain unset."); 55 | None 56 | } 57 | }; 58 | } 59 | } 60 | 61 | #[allow(clippy::too_many_arguments)] 62 | fn spectator_update( 63 | time: Res