Skip to content

Commit 33dff0d

Browse files
authored
2D top-down camera example (#12720)
# Objective This PR addresses the 2D part of #12658. I plan to separate the examples and make one PR per camera example. ## Solution Added a new top-down example composed of: - [x] Player keyboard movements - [x] UI for keyboard instructions - [x] Colors and bloom effect to see the movement of the player - [x] Camera smooth movement towards the player (lerp) ## Testing ```bash cargo run --features="wayland,bevy/dynamic_linking" --example 2d_top_down_camera ``` https://github.com/bevyengine/bevy/assets/10638479/95db0587-e5e0-4f55-be11-97444b795793
1 parent 1b9edd0 commit 33dff0d

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed

Cargo.toml

+11
Original file line numberDiff line numberDiff line change
@@ -3086,6 +3086,17 @@ path = "examples/dev_tools/fps_overlay.rs"
30863086
doc-scrape-examples = true
30873087
required-features = ["bevy_dev_tools"]
30883088

3089+
[[example]]
3090+
name = "2d_top_down_camera"
3091+
path = "examples/camera/2d_top_down_camera.rs"
3092+
doc-scrape-examples = true
3093+
3094+
[package.metadata.example.2d_top_down_camera]
3095+
name = "2D top-down camera"
3096+
description = "A 2D top-down camera smoothly following player movements"
3097+
category = "Camera"
3098+
wasm = true
3099+
30893100
[package.metadata.example.fps_overlay]
30903101
name = "FPS overlay"
30913102
description = "Demonstrates FPS overlay"

examples/README.md

+7
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ git checkout v0.4.0
4545
- [Assets](#assets)
4646
- [Async Tasks](#async-tasks)
4747
- [Audio](#audio)
48+
- [Camera](#camera)
4849
- [Dev tools](#dev-tools)
4950
- [Diagnostics](#diagnostics)
5051
- [ECS (Entity Component System)](#ecs-entity-component-system)
@@ -240,6 +241,12 @@ Example | Description
240241
[Spatial Audio 2D](../examples/audio/spatial_audio_2d.rs) | Shows how to play spatial audio, and moving the emitter in 2D
241242
[Spatial Audio 3D](../examples/audio/spatial_audio_3d.rs) | Shows how to play spatial audio, and moving the emitter in 3D
242243

244+
## Camera
245+
246+
Example | Description
247+
--- | ---
248+
[2D top-down camera](../examples/camera/2d_top_down_camera.rs) | A 2D top-down camera smoothly following player movements
249+
243250
## Dev tools
244251

245252
Example | Description

examples/camera/2d_top_down_camera.rs

+149
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//! This example showcases a 2D top-down camera with smooth player tracking.
2+
//!
3+
//! ## Controls
4+
//!
5+
//! | Key Binding | Action |
6+
//! |:---------------------|:--------------|
7+
//! | `Z`(azerty), `W`(US) | Move forward |
8+
//! | `S` | Move backward |
9+
//! | `Q`(azerty), `A`(US) | Move left |
10+
//! | `D` | Move right |
11+
12+
use bevy::core_pipeline::bloom::BloomSettings;
13+
use bevy::math::vec3;
14+
use bevy::prelude::*;
15+
use bevy::sprite::{MaterialMesh2dBundle, Mesh2dHandle};
16+
17+
/// Player movement speed factor.
18+
const PLAYER_SPEED: f32 = 100.;
19+
20+
/// Camera lerp factor.
21+
const CAM_LERP_FACTOR: f32 = 2.;
22+
23+
#[derive(Component)]
24+
struct Player;
25+
26+
fn main() {
27+
App::new()
28+
.add_plugins(DefaultPlugins)
29+
.add_systems(Startup, (setup_scene, setup_instructions, setup_camera))
30+
.add_systems(Update, (move_player, update_camera).chain())
31+
.run();
32+
}
33+
34+
fn setup_scene(
35+
mut commands: Commands,
36+
mut meshes: ResMut<Assets<Mesh>>,
37+
mut materials: ResMut<Assets<ColorMaterial>>,
38+
) {
39+
// World where we move the player
40+
commands.spawn(MaterialMesh2dBundle {
41+
mesh: Mesh2dHandle(meshes.add(Rectangle::new(1000., 700.))),
42+
material: materials.add(Color::srgb(0.2, 0.2, 0.3)),
43+
..default()
44+
});
45+
46+
// Player
47+
commands.spawn((
48+
Player,
49+
MaterialMesh2dBundle {
50+
mesh: meshes.add(Circle::new(25.)).into(),
51+
material: materials.add(Color::srgb(6.25, 9.4, 9.1)), // RGB values exceed 1 to achieve a bright color for the bloom effect
52+
transform: Transform {
53+
translation: vec3(0., 0., 2.),
54+
..default()
55+
},
56+
..default()
57+
},
58+
));
59+
}
60+
61+
fn setup_instructions(mut commands: Commands) {
62+
commands.spawn(
63+
TextBundle::from_section(
64+
"Move the light with ZQSD or WASD.\nThe camera will smoothly track the light.",
65+
TextStyle::default(),
66+
)
67+
.with_style(Style {
68+
position_type: PositionType::Absolute,
69+
bottom: Val::Px(12.0),
70+
left: Val::Px(12.0),
71+
..default()
72+
}),
73+
);
74+
}
75+
76+
fn setup_camera(mut commands: Commands) {
77+
commands.spawn((
78+
Camera2dBundle {
79+
camera: Camera {
80+
hdr: true, // HDR is required for the bloom effect
81+
..default()
82+
},
83+
..default()
84+
},
85+
BloomSettings::NATURAL,
86+
));
87+
}
88+
89+
/// Update the camera position by tracking the player.
90+
fn update_camera(
91+
mut camera: Query<&mut Transform, (With<Camera2d>, Without<Player>)>,
92+
player: Query<&Transform, (With<Player>, Without<Camera2d>)>,
93+
time: Res<Time>,
94+
) {
95+
let Ok(mut camera) = camera.get_single_mut() else {
96+
return;
97+
};
98+
99+
let Ok(player) = player.get_single() else {
100+
return;
101+
};
102+
103+
let Vec3 { x, y, .. } = player.translation;
104+
let direction = Vec3::new(x, y, camera.translation.z);
105+
106+
// Applies a smooth effect to camera movement using interpolation between
107+
// the camera position and the player position on the x and y axes.
108+
// Here we use the in-game time, to get the elapsed time (in seconds)
109+
// since the previous update. This avoids jittery movement when tracking
110+
// the player.
111+
camera.translation = camera
112+
.translation
113+
.lerp(direction, time.delta_seconds() * CAM_LERP_FACTOR);
114+
}
115+
116+
/// Update the player position with keyboard inputs.
117+
fn move_player(
118+
mut player: Query<&mut Transform, With<Player>>,
119+
time: Res<Time>,
120+
kb_input: Res<ButtonInput<KeyCode>>,
121+
) {
122+
let Ok(mut player) = player.get_single_mut() else {
123+
return;
124+
};
125+
126+
let mut direction = Vec2::ZERO;
127+
128+
if kb_input.pressed(KeyCode::KeyW) {
129+
direction.y += 1.;
130+
}
131+
132+
if kb_input.pressed(KeyCode::KeyS) {
133+
direction.y -= 1.;
134+
}
135+
136+
if kb_input.pressed(KeyCode::KeyA) {
137+
direction.x -= 1.;
138+
}
139+
140+
if kb_input.pressed(KeyCode::KeyD) {
141+
direction.x += 1.;
142+
}
143+
144+
// Progressively update the player's position over time. Normalize the
145+
// direction vector to prevent it from exceeding a magnitude of 1 when
146+
// moving diagonally.
147+
let move_delta = direction.normalize_or_zero() * PLAYER_SPEED * time.delta_seconds();
148+
player.translation += move_delta.extend(0.);
149+
}

0 commit comments

Comments
 (0)