Skip to main content

2d_top_down_camera/
2d_top_down_camera.rs

1//! This example showcases a 2D top-down camera with smooth player tracking.
2//!
3//! ## Controls
4//!
5//! | Key Binding          | Action        |
6//! |:---------------------|:--------------|
7//! | `W`                  | Move up       |
8//! | `S`                  | Move down     |
9//! | `A`                  | Move left     |
10//! | `D`                  | Move right    |
11
12use bevy::{post_process::bloom::Bloom, prelude::*};
13
14/// Player movement speed factor.
15const PLAYER_SPEED: f32 = 100.;
16
17/// How quickly should the camera snap to the desired location.
18const CAMERA_DECAY_RATE: f32 = 2.;
19
20#[derive(Component)]
21struct Player;
22
23fn main() {
24    App::new()
25        .add_plugins(DefaultPlugins)
26        .add_systems(Startup, (setup_scene, setup_instructions, setup_camera))
27        .add_systems(Update, (move_player, update_camera).chain())
28        .run();
29}
30
31fn setup_scene(
32    mut commands: Commands,
33    mut meshes: ResMut<Assets<Mesh>>,
34    mut materials: ResMut<Assets<ColorMaterial>>,
35) {
36    // World where we move the player
37    commands.spawn((
38        Mesh2d(meshes.add(Rectangle::new(1000., 700.))),
39        MeshMaterial2d(materials.add(Color::srgb(0.2, 0.2, 0.3))),
40    ));
41
42    // Player
43    commands.spawn((
44        Player,
45        Mesh2d(meshes.add(Circle::new(25.))),
46        MeshMaterial2d(materials.add(Color::srgb(6.25, 9.4, 9.1))), // RGB values exceed 1 to achieve a bright color for the bloom effect
47        Transform::from_xyz(0., 0., 2.),
48    ));
49}
50
51fn setup_instructions(mut commands: Commands) {
52    commands.spawn((
53        Text::new("Move the light with WASD.\nThe camera will smoothly track the light."),
54        Node {
55            position_type: PositionType::Absolute,
56            bottom: px(12),
57            left: px(12),
58            ..default()
59        },
60    ));
61}
62
63fn setup_camera(mut commands: Commands) {
64    commands.spawn((Camera2d, Bloom::NATURAL));
65}
66
67/// Update the camera position by tracking the player.
68fn update_camera(
69    mut camera: Single<&mut Transform, (With<Camera2d>, Without<Player>)>,
70    player: Single<&Transform, (With<Player>, Without<Camera2d>)>,
71    time: Res<Time>,
72) {
73    let Vec3 { x, y, .. } = player.translation;
74    let direction = Vec3::new(x, y, camera.translation.z);
75
76    // Applies a smooth effect to camera movement using stable interpolation
77    // between the camera position and the player position on the x and y axes.
78    camera
79        .translation
80        .smooth_nudge(&direction, CAMERA_DECAY_RATE, time.delta_secs());
81}
82
83/// Update the player position with keyboard inputs.
84/// Note that the approach used here is for demonstration purposes only,
85/// as the point of this example is to showcase the camera tracking feature.
86///
87/// A more robust solution for player movement can be found in `examples/movement/physics_in_fixed_timestep.rs`.
88fn move_player(
89    mut player: Single<&mut Transform, With<Player>>,
90    time: Res<Time>,
91    kb_input: Res<ButtonInput<KeyCode>>,
92) {
93    let mut direction = Vec2::ZERO;
94
95    if kb_input.pressed(KeyCode::KeyW) {
96        direction.y += 1.;
97    }
98
99    if kb_input.pressed(KeyCode::KeyS) {
100        direction.y -= 1.;
101    }
102
103    if kb_input.pressed(KeyCode::KeyA) {
104        direction.x -= 1.;
105    }
106
107    if kb_input.pressed(KeyCode::KeyD) {
108        direction.x += 1.;
109    }
110
111    // Progressively update the player's position over time. Normalize the
112    // direction vector to prevent it from exceeding a magnitude of 1 when
113    // moving diagonally.
114    let move_delta = direction.normalize_or_zero() * PLAYER_SPEED * time.delta_secs();
115    player.translation += move_delta.extend(0.);
116}