Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added keyboard movement #63

Merged
merged 4 commits into from
Aug 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Behaves similarly to common online map applications:

- Click and drag to move the camera
- Scroll to zoom
- Keep Keyboard buttons pushed to move the camera

## Usage

Expand All @@ -41,6 +42,13 @@ Alternatively, set the fields of the `PanCam` component to customize behavior:
commands.spawn(Camera2dBundle::default())
.insert(PanCam {
grab_buttons: vec![MouseButton::Left, MouseButton::Middle], // which buttons should drag the camera
move_keys: DirectionKeys { // the keyboard buttons used to move the camera
up: vec![KeyCode::KeyQ], // initalize the struct like this or use the provided methods for
down: vec![KeyCode::KeyW], // common key combinations
left: vec![KeyCode::KeyE],
right: vec![KeyCode::KeyR],
},
speed: 400., // the speed for the keyboard movement
enabled: true, // when false, controls are disabled. See toggle example.
zoom_to_cursor: true, // whether to zoom towards the mouse or the center of the screen
min_scale: 1., // prevent the camera from zooming too far in
Expand Down
120 changes: 110 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,89 @@ pub struct PanCamPlugin;
#[derive(Debug, Clone, Copy, SystemSet, PartialEq, Eq, Hash)]
pub struct PanCamSystemSet;

/// Which keys move the camera in particular directions for keyboard movement
#[derive(Debug, Clone, PartialEq, Eq, Hash, Reflect)]
pub struct DirectionKeys {
/// The keys that move the camera up
pub up: Vec<KeyCode>,
/// The keys that move the camera down
pub down: Vec<KeyCode>,
/// The keys that move the camera left
pub left: Vec<KeyCode>,
/// The keys that move the camera right
pub right: Vec<KeyCode>,
}

impl DirectionKeys {
/// No keys move the camera
pub const NONE: Self = Self {
up: vec![],
down: vec![],
left: vec![],
right: vec![],
};

/// The camera is moved by the arrow keys
pub fn arrows() -> Self {
Self {
up: vec![KeyCode::ArrowUp],
down: vec![KeyCode::ArrowDown],
left: vec![KeyCode::ArrowLeft],
right: vec![KeyCode::ArrowRight],
}
}

/// The camera is moved by the WASD keys
pub fn wasd() -> Self {
Self {
up: vec![KeyCode::KeyW],
down: vec![KeyCode::KeyS],
left: vec![KeyCode::KeyA],
right: vec![KeyCode::KeyD],
}
}

/// The camera is moved by the arrow and WASD keys
pub fn arrows_and_wasd() -> Self {
Self {
up: vec![KeyCode::ArrowUp, KeyCode::KeyW],
down: vec![KeyCode::ArrowDown, KeyCode::KeyS],
left: vec![KeyCode::ArrowLeft, KeyCode::KeyA],
right: vec![KeyCode::ArrowRight, KeyCode::KeyD],
}
}

fn direction(&self, keyboard_buttons: &Res<ButtonInput<KeyCode>>) -> Vec2 {
let mut direction = Vec2::ZERO;

if self.left.iter().any(|key| keyboard_buttons.pressed(*key)) {
direction.x -= 1.;
}

if self.right.iter().any(|key| keyboard_buttons.pressed(*key)) {
direction.x += 1.;
}

if self.up.iter().any(|key| keyboard_buttons.pressed(*key)) {
direction.y += 1.;
}

if self.down.iter().any(|key| keyboard_buttons.pressed(*key)) {
direction.y -= 1.;
}

direction
}
}
johanhelsing marked this conversation as resolved.
Show resolved Hide resolved

impl Plugin for PanCamPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(do_camera_movement, do_camera_zoom).in_set(PanCamSystemSet),
)
.register_type::<PanCam>();
.register_type::<PanCam>()
.register_type::<DirectionKeys>();

#[cfg(feature = "bevy_egui")]
{
Expand Down Expand Up @@ -179,8 +255,10 @@ fn clamp_to_safe_zone(pos: Vec2, aabb: Aabb2d, bounded_area_size: Vec2) -> Vec2
fn do_camera_movement(
primary_window: Query<&Window, With<PrimaryWindow>>,
mouse_buttons: Res<ButtonInput<MouseButton>>,
keyboard_buttons: Res<ButtonInput<KeyCode>>,
mut query: Query<(&PanCam, &mut Transform, &OrthographicProjection)>,
mut last_pos: Local<Option<Vec2>>,
time: Res<Time>,
) {
let Ok(window) = primary_window.get_single() else {
return;
Expand All @@ -196,21 +274,34 @@ fn do_camera_movement(
let delta_device_pixels = current_pos - last_pos.unwrap_or(current_pos);

for (cam, mut transform, projection) in &mut query {
if !cam.enabled
|| !cam
.grab_buttons
.iter()
.any(|btn| mouse_buttons.pressed(*btn) && !mouse_buttons.just_pressed(*btn))
{
if !cam.enabled {
continue;
}

let proj_area_size = projection.area.size();
let world_units_per_device_pixel = proj_area_size / window_size;

let mouse_delta = if !cam
.grab_buttons
.iter()
.any(|btn| mouse_buttons.pressed(*btn) && !mouse_buttons.just_pressed(*btn))
{
Vec2::ZERO
} else {
delta_device_pixels * proj_area_size / window_size
};

let direction = cam.move_keys.direction(&keyboard_buttons);

let keyboard_delta =
time.delta_seconds() * direction.normalize_or_zero() * cam.speed * projection.scale;
let delta = mouse_delta - keyboard_delta;

if delta == Vec2::ZERO {
continue;
}

// The proposed new camera position
let delta_world = delta_device_pixels * world_units_per_device_pixel;
let proposed_cam_pos = transform.translation.truncate() - delta_world;
let proposed_cam_pos = transform.translation.truncate() - delta;

transform.translation = clamp_to_safe_zone(proposed_cam_pos, cam.aabb(), proj_area_size)
.extend(transform.translation.z);
Expand All @@ -224,6 +315,13 @@ fn do_camera_movement(
pub struct PanCam {
/// The mouse buttons that will be used to drag and pan the camera
pub grab_buttons: Vec<MouseButton>,
/// The keyboard keys that will be used to move the camera
pub move_keys: DirectionKeys,
/// Speed for keyboard movement
johanhelsing marked this conversation as resolved.
Show resolved Hide resolved
///
/// This is multiplied with the projection scale of the camera so the
/// speed stays proportional to the current "zoom" level
pub speed: f32,
/// Whether camera currently responds to user input
pub enabled: bool,
/// When true, zooming the camera will center on the mouse cursor
Expand Down Expand Up @@ -296,6 +394,8 @@ impl PanCam {
impl Default for PanCam {
fn default() -> Self {
Self {
move_keys: DirectionKeys::arrows_and_wasd(),
speed: 200.,
grab_buttons: vec![MouseButton::Left, MouseButton::Right, MouseButton::Middle],
enabled: true,
zoom_to_cursor: true,
Expand Down
Loading