Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,9 @@ bevy_camera_controller = ["bevy_internal/bevy_camera_controller"]
# Enables the free cam from bevy_camera_controller
free_cam = ["bevy_internal/free_cam"]

# Enables the pan cam from bevy_camera_controller
pan_cam = ["bevy_internal/pan_cam"]

# Enable the Bevy Remote Protocol
bevy_remote = ["bevy_internal/bevy_remote"]

Expand Down Expand Up @@ -4826,3 +4829,15 @@ name = "Render Depth to Texture"
description = "Demonstrates how to use depth-only cameras"
category = "Shaders"
wasm = true

[[example]]
name = "pan_cam"
path = "examples/helpers/pan_cam.rs"
doc-scrape-examples = true
required-features = ["pan_cam"]

[package.metadata.example.pan_cam]
name = "Pan Cam"
description = "Example Pan-Cam Styled Camera Controller for 2D scenes"
category = "Helpers"
wasm = true
1 change: 1 addition & 0 deletions crates/bevy_camera_controller/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ libm = ["bevy_math/libm"]

# Camera controllers
free_cam = []
pan_cam = []

[lints]
workspace = true
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_camera_controller/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@

#[cfg(feature = "free_cam")]
pub mod free_cam;

#[cfg(feature = "pan_cam")]
pub mod pan_cam;
166 changes: 166 additions & 0 deletions crates/bevy_camera_controller/src/pan_cam.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
//! A camera controller for 2D scenes that supports panning and zooming.
//!
//! To use this controller, add [`PanCamPlugin`] to your app,
//! and insert a [`PanCam`] component into your camera entity.
//!
//! To configure the settings of this controller, modify the fields of the [`PanCam`] component.

use bevy_app::{App, Plugin, RunFixedMainLoop, RunFixedMainLoopSystems};
use bevy_camera::Camera;
use bevy_ecs::prelude::*;
use bevy_input::keyboard::KeyCode;
use bevy_input::ButtonInput;
use bevy_math::{Vec2, Vec3};
use bevy_time::{Real, Time};
use bevy_transform::prelude::Transform;

use core::{f32::consts::*, fmt};

/// A pancam-style camera controller plugin.
///
/// Use [`PanCam`] to add a pancam controller to a camera entity,
/// and change its values to customize the controls and change its behavior.
pub struct PanCamPlugin;

impl Plugin for PanCamPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
RunFixedMainLoop,
run_pancam_controller.in_set(RunFixedMainLoopSystems::BeforeFixedMainLoop),
);
}
}

/// Pancam controller settings and state.
///
/// Add this component to a [`Camera`] entity and add [`PanCamPlugin`]
/// to your [`App`] to enable freecam controls.
#[derive(Component)]
pub struct PanCam {
/// Enables this [`PanCam`] when `true`.
pub enable: bool,
/// Multiplier for how much each zoom input affects the camera.
pub zoom_factor: f32,
/// Minimum allowed zoom level.
pub min_zoom: f32,
/// Maximum allowed zoom level.
pub max_zoom: f32,
/// [`KeyCode`] to zoom in.
pub key_zoom_in: KeyCode,
/// [`KeyCode`] to zoom out.
pub key_zoom_out: KeyCode,
/// This [`PanCam`]'s translation speed.
pub pan_speed: f32,
/// [`KeyCode`] for upward translation.
pub key_up: KeyCode,
/// [`KeyCode`] for backward translation.
pub key_down: KeyCode,
/// [`KeyCode`] for leftward translation.
pub key_left: KeyCode,
/// [`KeyCode`] for rightward translation.
pub key_right: KeyCode,
/// Rotation speed multiplier (in radians per second).
pub rotation_speed: f32,
/// [`KeyCode`] for counter-clockwise rotation.
pub key_rotate_ccw: KeyCode,
/// [`KeyCode`] for clockwise rotation.
pub key_rotate_cw: KeyCode,
}

impl Default for PanCam {
fn default() -> Self {
Self {
enable: true,
zoom_factor: 0.1,
min_zoom: 0.2,
max_zoom: 5.0,
key_zoom_in: KeyCode::Equal,
key_zoom_out: KeyCode::Minus,
pan_speed: 500.0,
key_up: KeyCode::KeyW,
key_down: KeyCode::KeyS,
key_left: KeyCode::KeyA,
key_right: KeyCode::KeyD,
rotation_speed: PI,
key_rotate_ccw: KeyCode::KeyQ,
key_rotate_cw: KeyCode::KeyE,
}
}
}

impl fmt::Display for PanCam {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"
PanCam Controls:
{:?} & {:?}\t- Move up & down
{:?} & {:?}\t- Move left & right
{:?}\t- Rotate counter-clockwise
{:?}\t- Rotate clockwise
Mouse Scroll\t- Zoom in & out
{:?} & {:?}\t- Zoom in & out keys",
self.key_up,
self.key_down,
self.key_left,
self.key_right,
self.key_rotate_ccw,
self.key_rotate_cw,
self.key_zoom_in,
self.key_zoom_out,
)
}
}

/// This system is typically added via the [`PanCamPlugin`].
///
/// Reads inputs and then moves the camera entity according
/// to the settings given in [`PanCam`].
fn run_pancam_controller(
time: Res<Time<Real>>,
key_input: Res<ButtonInput<KeyCode>>,
mut query: Query<(&mut Transform, &PanCam), With<Camera>>,
) {
let dt = time.delta_secs();

let Ok((mut transform, controller)) = query.single_mut() else {
return;
};

if !controller.enable {
return;
}

// === Movement
let mut movement = Vec2::ZERO;
if key_input.pressed(controller.key_left) {
movement.x -= 1.0;
}
if key_input.pressed(controller.key_right) {
movement.x += 1.0;
}
if key_input.pressed(controller.key_down) {
movement.y -= 1.0;
}
if key_input.pressed(controller.key_up) {
movement.y += 1.0;
}

// NOTE: Movement is world-axis aligned, not relative to camera rotation
if movement != Vec2::ZERO {
let delta = movement.normalize() * controller.pan_speed * dt;
transform.translation.x += delta.x;
transform.translation.y += delta.y;
}

// === Rotation
if key_input.pressed(controller.key_rotate_ccw) {
transform.rotate_z(controller.rotation_speed * dt);
}
if key_input.pressed(controller.key_rotate_cw) {
transform.rotate_z(-controller.rotation_speed * dt);
}

// === Zoom
// TODO: Implement zooming (e.g., adjusting camera scale or projection)
}
1 change: 1 addition & 0 deletions crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ 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"]
pan_cam = ["bevy_camera_controller/pan_cam"]

# Enable support for the Bevy Remote Protocol
bevy_remote = ["dep:bevy_remote", "serialize"]
Expand Down
19 changes: 19 additions & 0 deletions examples/helpers/pan_cam.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//! Example for `PanCam`, displays a single [`Sprite`], created from an image.

use bevy::prelude::*;
use bevy::camera_controller::pan_cam::{PanCam, PanCamPlugin};

fn main() {
App::new()
.add_plugins((DefaultPlugins, PanCamPlugin))
.add_systems(Startup, setup)
.run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn((Camera2d, PanCam::default()));

commands.spawn(Sprite::from_image(
asset_server.load("branding/bevy_bird_dark.png"),
));
}
Loading