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

2D top-down camera example #12720

Merged
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3086,6 +3086,17 @@ path = "examples/dev_tools/fps_overlay.rs"
doc-scrape-examples = true
required-features = ["bevy_dev_tools"]

[[example]]
name = "2d_top_down_camera"
path = "examples/camera/2d_top_down_camera.rs"
doc-scrape-examples = true

[package.metadata.example.2d_top_down_camera]
name = "2D top-down camera"
description = "A 2D top-down camera smoothly following player movements"
category = "Camera"
wasm = true

[package.metadata.example.fps_overlay]
name = "FPS overlay"
description = "Demonstrates FPS overlay"
Expand Down
7 changes: 7 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ git checkout v0.4.0
- [Assets](#assets)
- [Async Tasks](#async-tasks)
- [Audio](#audio)
- [Camera](#camera)
- [Dev tools](#dev-tools)
- [Diagnostics](#diagnostics)
- [ECS (Entity Component System)](#ecs-entity-component-system)
Expand Down Expand Up @@ -240,6 +241,12 @@ Example | Description
[Spatial Audio 2D](../examples/audio/spatial_audio_2d.rs) | Shows how to play spatial audio, and moving the emitter in 2D
[Spatial Audio 3D](../examples/audio/spatial_audio_3d.rs) | Shows how to play spatial audio, and moving the emitter in 3D

## Camera

Example | Description
--- | ---
[2D top-down camera](../examples/camera/2d_top_down_camera.rs) | A 2D top-down camera smoothly following player movements

## Dev tools

Example | Description
Expand Down
149 changes: 149 additions & 0 deletions examples/camera/2d_top_down_camera.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
//! This example showcases a 2D top-down camera with smooth player tracking.
//!
//! ## Controls
//!
//! | Key Binding | Action |
//! |:---------------------|:--------------|
//! | `Z`(azerty), `W`(US) | Move forward |
//! | `S` | Move backward |
//! | `Q`(azerty), `A`(US) | Move left |
//! | `D` | Move right |

use bevy::core_pipeline::bloom::BloomSettings;
use bevy::math::vec3;
use bevy::prelude::*;
use bevy::sprite::{MaterialMesh2dBundle, Mesh2dHandle};

/// Player movement speed factor.
const PLAYER_SPEED: f32 = 100.;

/// Camera lerp factor.
const CAM_LERP_FACTOR: f32 = 2.;

#[derive(Component)]
struct Player;

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, (setup_scene, setup_instructions, setup_camera))
.add_systems(Update, (move_player, update_camera).chain())
.run();
}

fn setup_scene(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<ColorMaterial>>,
) {
// World where we move the player
commands.spawn(MaterialMesh2dBundle {
mesh: Mesh2dHandle(meshes.add(Rectangle::new(1000., 700.))),
material: materials.add(Color::srgb(0.2, 0.2, 0.3)),
..default()
});

// Player
commands.spawn((
Player,
MaterialMesh2dBundle {
mesh: meshes.add(Circle::new(25.)).into(),
material: materials.add(Color::srgb(6.25, 9.4, 9.1)), // RGB values exceed 1 to achieve a bright color for the bloom effect
transform: Transform {
translation: vec3(0., 0., 2.),
..default()
},
..default()
},
));
}

fn setup_instructions(mut commands: Commands) {
commands.spawn(
TextBundle::from_section(
"Move the light with ZQSD or WASD.\nThe camera will smoothly track the light.",
TextStyle::default(),
)
.with_style(Style {
position_type: PositionType::Absolute,
bottom: Val::Px(12.0),
left: Val::Px(12.0),
..default()
}),
);
}

fn setup_camera(mut commands: Commands) {
commands.spawn((
Camera2dBundle {
camera: Camera {
hdr: true, // HDR is required for the bloom effect
..default()
},
..default()
},
BloomSettings::NATURAL,
));
}

/// Update the camera position by tracking the player.
fn update_camera(
mut camera: Query<&mut Transform, (With<Camera2d>, Without<Player>)>,
player: Query<&Transform, (With<Player>, Without<Camera2d>)>,
time: Res<Time>,
) {
let Ok(mut camera) = camera.get_single_mut() else {
return;
};

let Ok(player) = player.get_single() else {
return;
};

let Vec3 { x, y, .. } = player.translation;
let direction = Vec3::new(x, y, camera.translation.z);

// Applies a smooth effect to camera movement using interpolation between
// the camera position and the player position on the x and y axes.
// Here we use the in-game time, to get the elapsed time (in seconds)
// since the previous update. This avoids jittery movement when tracking
// the player.
camera.translation = camera
.translation
.lerp(direction, time.delta_seconds() * CAM_LERP_FACTOR);
}

/// Update the player position with keyboard inputs.
fn move_player(
mut player: Query<&mut Transform, With<Player>>,
time: Res<Time>,
kb_input: Res<ButtonInput<KeyCode>>,
) {
let Ok(mut player) = player.get_single_mut() else {
return;
};

let mut direction = Vec2::ZERO;

if kb_input.pressed(KeyCode::KeyW) {
direction.y += 1.;
}

if kb_input.pressed(KeyCode::KeyS) {
direction.y -= 1.;
}

if kb_input.pressed(KeyCode::KeyA) {
direction.x -= 1.;
}

if kb_input.pressed(KeyCode::KeyD) {
direction.x += 1.;
}

// Progressively update the player's position over time. Normalize the
// direction vector to prevent it from exceeding a magnitude of 1 when
// moving diagonally.
let move_delta = direction.normalize_or_zero() * PLAYER_SPEED * time.delta_seconds();
player.translation += move_delta.extend(0.);
}