Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/2d/2d_viewport_to_world.rs
9376 views
1
//! This example demonstrates how to use the `Camera::viewport_to_world_2d` method with a dynamic viewport and camera.
2
3
use bevy::{
4
camera::Viewport,
5
color::palettes::{
6
basic::WHITE,
7
css::{GREEN, RED},
8
},
9
math::ops::powf,
10
prelude::*,
11
};
12
13
fn main() {
14
App::new()
15
.add_plugins(DefaultPlugins)
16
.add_systems(Startup, setup)
17
.add_systems(FixedUpdate, controls)
18
.add_systems(PostUpdate, draw_cursor.after(TransformSystems::Propagate))
19
.run();
20
}
21
22
fn draw_cursor(
23
camera_query: Single<(&Camera, &GlobalTransform)>,
24
window: Single<&Window>,
25
mut gizmos: Gizmos,
26
) {
27
let (camera, camera_transform) = *camera_query;
28
29
if let Some(cursor_position) = window.cursor_position()
30
// Calculate a world position based on the cursor's position.
31
&& let Ok(world_pos) = camera.viewport_to_world_2d(camera_transform, cursor_position)
32
// To test Camera::world_to_viewport, convert result back to viewport space and then back to world space.
33
&& let Ok(viewport_check) = camera.world_to_viewport(camera_transform, world_pos.extend(0.0))
34
&& let Ok(world_check) = camera.viewport_to_world_2d(camera_transform, viewport_check.xy())
35
{
36
gizmos.circle_2d(world_pos, 10., WHITE);
37
// Should be the same as world_pos
38
gizmos.circle_2d(world_check, 8., RED);
39
}
40
}
41
42
fn controls(
43
camera_query: Single<(&mut Camera, &mut Transform, &mut Projection)>,
44
window: Single<&Window>,
45
input: Res<ButtonInput<KeyCode>>,
46
time: Res<Time<Fixed>>,
47
) {
48
let (mut camera, mut transform, mut projection) = camera_query.into_inner();
49
50
let fspeed = 600.0 * time.delta_secs();
51
let uspeed = fspeed as u32;
52
let window_size = window.resolution.physical_size();
53
54
// Camera movement controls
55
if input.pressed(KeyCode::ArrowUp) {
56
transform.translation.y += fspeed;
57
}
58
if input.pressed(KeyCode::ArrowDown) {
59
transform.translation.y -= fspeed;
60
}
61
if input.pressed(KeyCode::ArrowLeft) {
62
transform.translation.x -= fspeed;
63
}
64
if input.pressed(KeyCode::ArrowRight) {
65
transform.translation.x += fspeed;
66
}
67
68
// Camera zoom controls
69
if let Projection::Orthographic(projection2d) = &mut *projection {
70
if input.pressed(KeyCode::Comma) {
71
projection2d.scale *= powf(4.0f32, time.delta_secs());
72
}
73
74
if input.pressed(KeyCode::Period) {
75
projection2d.scale *= powf(0.25f32, time.delta_secs());
76
}
77
}
78
79
if let Some(viewport) = camera.viewport.as_mut() {
80
// Reset viewport size on window resize
81
if viewport.physical_size.x > window_size.x || viewport.physical_size.y > window_size.y {
82
viewport.physical_size = (window_size.as_vec2() * 0.75).as_uvec2();
83
}
84
85
// Viewport movement controls
86
if input.pressed(KeyCode::KeyW) {
87
viewport.physical_position.y = viewport.physical_position.y.saturating_sub(uspeed);
88
}
89
if input.pressed(KeyCode::KeyS) {
90
viewport.physical_position.y += uspeed;
91
}
92
if input.pressed(KeyCode::KeyA) {
93
viewport.physical_position.x = viewport.physical_position.x.saturating_sub(uspeed);
94
}
95
if input.pressed(KeyCode::KeyD) {
96
viewport.physical_position.x += uspeed;
97
}
98
99
// Bound viewport position so it doesn't go off-screen
100
viewport.physical_position = viewport
101
.physical_position
102
.min(window_size - viewport.physical_size);
103
104
// Viewport size controls
105
if input.pressed(KeyCode::KeyI) {
106
viewport.physical_size.y = viewport.physical_size.y.saturating_sub(uspeed);
107
}
108
if input.pressed(KeyCode::KeyK) {
109
viewport.physical_size.y += uspeed;
110
}
111
if input.pressed(KeyCode::KeyJ) {
112
viewport.physical_size.x = viewport.physical_size.x.saturating_sub(uspeed);
113
}
114
if input.pressed(KeyCode::KeyL) {
115
viewport.physical_size.x += uspeed;
116
}
117
118
// Bound viewport size so it doesn't go off-screen
119
viewport.physical_size = viewport
120
.physical_size
121
.min(window_size - viewport.physical_position)
122
.max(UVec2::new(20, 20));
123
}
124
}
125
126
fn setup(
127
mut commands: Commands,
128
mut meshes: ResMut<Assets<Mesh>>,
129
mut materials: ResMut<Assets<ColorMaterial>>,
130
window: Single<&Window>,
131
) {
132
let window_size = window.resolution.physical_size().as_vec2();
133
134
// Initialize centered, non-window-filling viewport
135
commands.spawn((
136
Camera2d,
137
Camera {
138
viewport: Some(Viewport {
139
physical_position: (window_size * 0.125).as_uvec2(),
140
physical_size: (window_size * 0.75).as_uvec2(),
141
..default()
142
}),
143
..default()
144
},
145
));
146
147
// Create a minimal UI explaining how to interact with the example
148
commands.spawn((
149
Text::new(
150
"Move the mouse to see the circle follow your cursor.\n\
151
Use the arrow keys to move the camera.\n\
152
Use the comma and period keys to zoom in and out.\n\
153
Use the WASD keys to move the viewport.\n\
154
Use the IJKL keys to resize the viewport.",
155
),
156
Node {
157
position_type: PositionType::Absolute,
158
top: px(12),
159
left: px(12),
160
..default()
161
},
162
));
163
164
// Add mesh to make camera movement visible
165
commands.spawn((
166
Mesh2d(meshes.add(Rectangle::new(40.0, 20.0))),
167
MeshMaterial2d(materials.add(Color::from(GREEN))),
168
));
169
170
// Add background to visualize viewport bounds
171
commands.spawn((
172
Mesh2d(meshes.add(Rectangle::new(50000.0, 50000.0))),
173
MeshMaterial2d(materials.add(Color::linear_rgb(0.01, 0.01, 0.01))),
174
Transform::from_translation(Vec3::new(0.0, 0.0, -200.0)),
175
));
176
}
177
178