Skip to content

Commit 9bd7b58

Browse files
committed
Extend viewport conversion example with dynamic viewport
1 parent 4c519dd commit 9bd7b58

File tree

1 file changed

+152
-10
lines changed

1 file changed

+152
-10
lines changed

examples/2d/2d_viewport_to_world.rs

+152-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
1-
//! This example demonstrates how to use the `Camera::viewport_to_world_2d` method.
1+
//! This example demonstrates how to use the `Camera::viewport_to_world_2d` method with a dynamic viewport and camera.
22
3-
use bevy::{color::palettes::basic::WHITE, prelude::*};
3+
use bevy::{
4+
color::palettes::{
5+
basic::WHITE,
6+
css::{GREEN, RED},
7+
},
8+
math::ops::powf,
9+
prelude::*,
10+
render::camera::Viewport,
11+
};
412

513
fn main() {
614
App::new()
715
.add_plugins(DefaultPlugins)
816
.add_systems(Startup, setup)
9-
.add_systems(Update, draw_cursor)
17+
.add_systems(FixedUpdate, controls)
18+
.add_systems(
19+
PostUpdate,
20+
draw_cursor.after(TransformSystem::TransformPropagate),
21+
)
1022
.run();
1123
}
1224

@@ -15,35 +27,165 @@ fn draw_cursor(
1527
window: Query<&Window>,
1628
mut gizmos: Gizmos,
1729
) {
30+
let (camera, camera_transform) = *camera_query;
1831
let Ok(window) = window.get_single() else {
1932
return;
2033
};
2134

22-
let (camera, camera_transform) = *camera_query;
23-
2435
let Some(cursor_position) = window.cursor_position() else {
2536
return;
2637
};
2738

2839
// Calculate a world position based on the cursor's position.
29-
let Ok(point) = camera.viewport_to_world_2d(camera_transform, cursor_position) else {
40+
let Ok(world_pos) = camera.viewport_to_world_2d(camera_transform, cursor_position) else {
41+
return;
42+
};
43+
44+
// To test Camera::world_to_viewport, convert result back to viewport space and then back to world space.
45+
let Ok(viewport_check) = camera.world_to_viewport(camera_transform, world_pos.extend(0.0))
46+
else {
47+
return;
48+
};
49+
let Ok(world_check) = camera.viewport_to_world_2d(camera_transform, viewport_check.xy()) else {
50+
return;
51+
};
52+
53+
gizmos.circle_2d(world_pos, 10., WHITE);
54+
// Should be the same as world_pos
55+
gizmos.circle_2d(world_check, 8., RED);
56+
}
57+
58+
fn controls(
59+
mut camera_query: Query<(&mut Camera, &mut Transform, &mut Projection)>,
60+
window: Query<&Window>,
61+
input: Res<ButtonInput<KeyCode>>,
62+
time: Res<Time<Fixed>>,
63+
) {
64+
let Ok(window) = window.get_single() else {
65+
return;
66+
};
67+
let Ok((mut camera, mut transform, mut projection)) = camera_query.get_single_mut() else {
3068
return;
3169
};
70+
let fspeed = 600.0 * time.delta_secs();
71+
let uspeed = fspeed as u32;
72+
let window_size = window.resolution.physical_size();
3273

33-
gizmos.circle_2d(point, 10., WHITE);
74+
// Camera movement controls
75+
if input.pressed(KeyCode::ArrowUp) {
76+
transform.translation.y += fspeed;
77+
}
78+
if input.pressed(KeyCode::ArrowDown) {
79+
transform.translation.y -= fspeed;
80+
}
81+
if input.pressed(KeyCode::ArrowLeft) {
82+
transform.translation.x -= fspeed;
83+
}
84+
if input.pressed(KeyCode::ArrowRight) {
85+
transform.translation.x += fspeed;
86+
}
87+
88+
// Camera zoom controls
89+
if let Projection::Orthographic(projection2d) = &mut *projection {
90+
if input.pressed(KeyCode::Comma) {
91+
projection2d.scale *= powf(4.0f32, time.delta_secs());
92+
}
93+
94+
if input.pressed(KeyCode::Period) {
95+
projection2d.scale *= powf(0.25f32, time.delta_secs());
96+
}
97+
}
98+
99+
if let Some(viewport) = camera.viewport.as_mut() {
100+
// Viewport movement controls
101+
if input.pressed(KeyCode::KeyW) {
102+
viewport.physical_position.y = viewport.physical_position.y.saturating_sub(uspeed);
103+
}
104+
if input.pressed(KeyCode::KeyS) {
105+
viewport.physical_position.y += uspeed;
106+
}
107+
if input.pressed(KeyCode::KeyA) {
108+
viewport.physical_position.x = viewport.physical_position.x.saturating_sub(uspeed);
109+
}
110+
if input.pressed(KeyCode::KeyD) {
111+
viewport.physical_position.x += uspeed;
112+
}
113+
114+
// Bound viewport position so it doesn't go off-screen
115+
viewport.physical_position = viewport
116+
.physical_position
117+
.min(window_size - viewport.physical_size);
118+
119+
// Viewport size controls
120+
if input.pressed(KeyCode::KeyI) {
121+
viewport.physical_size.y = viewport.physical_size.y.saturating_sub(uspeed);
122+
}
123+
if input.pressed(KeyCode::KeyK) {
124+
viewport.physical_size.y += uspeed;
125+
}
126+
if input.pressed(KeyCode::KeyJ) {
127+
viewport.physical_size.x = viewport.physical_size.x.saturating_sub(uspeed);
128+
}
129+
if input.pressed(KeyCode::KeyL) {
130+
viewport.physical_size.x += uspeed;
131+
}
132+
133+
// Bound viewport size so it doesn't go off-screen
134+
viewport.physical_size = viewport
135+
.physical_size
136+
.min(window_size - viewport.physical_position)
137+
.max(UVec2::new(20, 20));
138+
}
34139
}
35140

36-
fn setup(mut commands: Commands) {
37-
commands.spawn(Camera2d);
141+
fn setup(
142+
mut commands: Commands,
143+
mut meshes: ResMut<Assets<Mesh>>,
144+
mut materials: ResMut<Assets<ColorMaterial>>,
145+
window: Single<&Window>,
146+
) {
147+
let window_size = window.resolution.physical_size().as_vec2();
148+
149+
// Initialize centered, non-window-filling viewport
150+
commands.spawn((
151+
Camera2d,
152+
Camera {
153+
viewport: Some(Viewport {
154+
physical_position: (window_size * 0.125).as_uvec2(),
155+
physical_size: (window_size * 0.75).as_uvec2(),
156+
..default()
157+
}),
158+
..default()
159+
},
160+
));
38161

39162
// Create a minimal UI explaining how to interact with the example
40163
commands.spawn((
41-
Text::new("Move the mouse to see the circle follow your cursor."),
164+
Text::new(
165+
"Move the mouse to see the circle follow your cursor.\n\
166+
Use the arrow keys to move the camera.\n\
167+
Use the comma and period keys to zoom in and out.\n\
168+
Use the WASD keys to move the viewport.\n\
169+
Use the IJKL keys to resize the viewport.",
170+
),
42171
Node {
43172
position_type: PositionType::Absolute,
44173
top: Val::Px(12.0),
45174
left: Val::Px(12.0),
46175
..default()
47176
},
48177
));
178+
179+
// Add mesh to make camera movement visible
180+
commands.spawn((
181+
Mesh2d(meshes.add(Rectangle::new(40.0, 20.0))),
182+
MeshMaterial2d(materials.add(Color::from(GREEN))),
183+
));
184+
185+
// Add background to visualize viewport bounds
186+
commands.spawn((
187+
Mesh2d(meshes.add(Rectangle::new(50000.0, 50000.0))),
188+
MeshMaterial2d(materials.add(Color::linear_rgb(0.01, 0.01, 0.01))),
189+
Transform::from_translation(Vec3::new(0.0, 0.0, -200.0)),
190+
));
49191
}

0 commit comments

Comments
 (0)