Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/2d/2d_viewport_to_world.rs
6592 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
// Viewport movement controls
81
if input.pressed(KeyCode::KeyW) {
82
viewport.physical_position.y = viewport.physical_position.y.saturating_sub(uspeed);
83
}
84
if input.pressed(KeyCode::KeyS) {
85
viewport.physical_position.y += uspeed;
86
}
87
if input.pressed(KeyCode::KeyA) {
88
viewport.physical_position.x = viewport.physical_position.x.saturating_sub(uspeed);
89
}
90
if input.pressed(KeyCode::KeyD) {
91
viewport.physical_position.x += uspeed;
92
}
93
94
// Bound viewport position so it doesn't go off-screen
95
viewport.physical_position = viewport
96
.physical_position
97
.min(window_size - viewport.physical_size);
98
99
// Viewport size controls
100
if input.pressed(KeyCode::KeyI) {
101
viewport.physical_size.y = viewport.physical_size.y.saturating_sub(uspeed);
102
}
103
if input.pressed(KeyCode::KeyK) {
104
viewport.physical_size.y += uspeed;
105
}
106
if input.pressed(KeyCode::KeyJ) {
107
viewport.physical_size.x = viewport.physical_size.x.saturating_sub(uspeed);
108
}
109
if input.pressed(KeyCode::KeyL) {
110
viewport.physical_size.x += uspeed;
111
}
112
113
// Bound viewport size so it doesn't go off-screen
114
viewport.physical_size = viewport
115
.physical_size
116
.min(window_size - viewport.physical_position)
117
.max(UVec2::new(20, 20));
118
}
119
}
120
121
fn setup(
122
mut commands: Commands,
123
mut meshes: ResMut<Assets<Mesh>>,
124
mut materials: ResMut<Assets<ColorMaterial>>,
125
window: Single<&Window>,
126
) {
127
let window_size = window.resolution.physical_size().as_vec2();
128
129
// Initialize centered, non-window-filling viewport
130
commands.spawn((
131
Camera2d,
132
Camera {
133
viewport: Some(Viewport {
134
physical_position: (window_size * 0.125).as_uvec2(),
135
physical_size: (window_size * 0.75).as_uvec2(),
136
..default()
137
}),
138
..default()
139
},
140
));
141
142
// Create a minimal UI explaining how to interact with the example
143
commands.spawn((
144
Text::new(
145
"Move the mouse to see the circle follow your cursor.\n\
146
Use the arrow keys to move the camera.\n\
147
Use the comma and period keys to zoom in and out.\n\
148
Use the WASD keys to move the viewport.\n\
149
Use the IJKL keys to resize the viewport.",
150
),
151
Node {
152
position_type: PositionType::Absolute,
153
top: px(12),
154
left: px(12),
155
..default()
156
},
157
));
158
159
// Add mesh to make camera movement visible
160
commands.spawn((
161
Mesh2d(meshes.add(Rectangle::new(40.0, 20.0))),
162
MeshMaterial2d(materials.add(Color::from(GREEN))),
163
));
164
165
// Add background to visualize viewport bounds
166
commands.spawn((
167
Mesh2d(meshes.add(Rectangle::new(50000.0, 50000.0))),
168
MeshMaterial2d(materials.add(Color::linear_rgb(0.01, 0.01, 0.01))),
169
Transform::from_translation(Vec3::new(0.0, 0.0, -200.0)),
170
));
171
}
172
173