
# Objective Make use of let chains to reduce LoC where we previously used let else to cut down on indentation. Best of both worlds. Hmm, déjà vu?
172 lines
5.7 KiB
Rust
172 lines
5.7 KiB
Rust
//! This example demonstrates how to use the `Camera::viewport_to_world_2d` method with a dynamic viewport and camera.
|
|
|
|
use bevy::{
|
|
color::palettes::{
|
|
basic::WHITE,
|
|
css::{GREEN, RED},
|
|
},
|
|
math::ops::powf,
|
|
prelude::*,
|
|
render::camera::Viewport,
|
|
};
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
.add_systems(Startup, setup)
|
|
.add_systems(FixedUpdate, controls)
|
|
.add_systems(PostUpdate, draw_cursor.after(TransformSystems::Propagate))
|
|
.run();
|
|
}
|
|
|
|
fn draw_cursor(
|
|
camera_query: Single<(&Camera, &GlobalTransform)>,
|
|
window: Single<&Window>,
|
|
mut gizmos: Gizmos,
|
|
) {
|
|
let (camera, camera_transform) = *camera_query;
|
|
|
|
if let Some(cursor_position) = window.cursor_position()
|
|
// Calculate a world position based on the cursor's position.
|
|
&& let Ok(world_pos) = camera.viewport_to_world_2d(camera_transform, cursor_position)
|
|
// To test Camera::world_to_viewport, convert result back to viewport space and then back to world space.
|
|
&& let Ok(viewport_check) = camera.world_to_viewport(camera_transform, world_pos.extend(0.0))
|
|
&& let Ok(world_check) = camera.viewport_to_world_2d(camera_transform, viewport_check.xy())
|
|
{
|
|
gizmos.circle_2d(world_pos, 10., WHITE);
|
|
// Should be the same as world_pos
|
|
gizmos.circle_2d(world_check, 8., RED);
|
|
}
|
|
}
|
|
|
|
fn controls(
|
|
camera_query: Single<(&mut Camera, &mut Transform, &mut Projection)>,
|
|
window: Single<&Window>,
|
|
input: Res<ButtonInput<KeyCode>>,
|
|
time: Res<Time<Fixed>>,
|
|
) {
|
|
let (mut camera, mut transform, mut projection) = camera_query.into_inner();
|
|
|
|
let fspeed = 600.0 * time.delta_secs();
|
|
let uspeed = fspeed as u32;
|
|
let window_size = window.resolution.physical_size();
|
|
|
|
// Camera movement controls
|
|
if input.pressed(KeyCode::ArrowUp) {
|
|
transform.translation.y += fspeed;
|
|
}
|
|
if input.pressed(KeyCode::ArrowDown) {
|
|
transform.translation.y -= fspeed;
|
|
}
|
|
if input.pressed(KeyCode::ArrowLeft) {
|
|
transform.translation.x -= fspeed;
|
|
}
|
|
if input.pressed(KeyCode::ArrowRight) {
|
|
transform.translation.x += fspeed;
|
|
}
|
|
|
|
// Camera zoom controls
|
|
if let Projection::Orthographic(projection2d) = &mut *projection {
|
|
if input.pressed(KeyCode::Comma) {
|
|
projection2d.scale *= powf(4.0f32, time.delta_secs());
|
|
}
|
|
|
|
if input.pressed(KeyCode::Period) {
|
|
projection2d.scale *= powf(0.25f32, time.delta_secs());
|
|
}
|
|
}
|
|
|
|
if let Some(viewport) = camera.viewport.as_mut() {
|
|
// Viewport movement controls
|
|
if input.pressed(KeyCode::KeyW) {
|
|
viewport.physical_position.y = viewport.physical_position.y.saturating_sub(uspeed);
|
|
}
|
|
if input.pressed(KeyCode::KeyS) {
|
|
viewport.physical_position.y += uspeed;
|
|
}
|
|
if input.pressed(KeyCode::KeyA) {
|
|
viewport.physical_position.x = viewport.physical_position.x.saturating_sub(uspeed);
|
|
}
|
|
if input.pressed(KeyCode::KeyD) {
|
|
viewport.physical_position.x += uspeed;
|
|
}
|
|
|
|
// Bound viewport position so it doesn't go off-screen
|
|
viewport.physical_position = viewport
|
|
.physical_position
|
|
.min(window_size - viewport.physical_size);
|
|
|
|
// Viewport size controls
|
|
if input.pressed(KeyCode::KeyI) {
|
|
viewport.physical_size.y = viewport.physical_size.y.saturating_sub(uspeed);
|
|
}
|
|
if input.pressed(KeyCode::KeyK) {
|
|
viewport.physical_size.y += uspeed;
|
|
}
|
|
if input.pressed(KeyCode::KeyJ) {
|
|
viewport.physical_size.x = viewport.physical_size.x.saturating_sub(uspeed);
|
|
}
|
|
if input.pressed(KeyCode::KeyL) {
|
|
viewport.physical_size.x += uspeed;
|
|
}
|
|
|
|
// Bound viewport size so it doesn't go off-screen
|
|
viewport.physical_size = viewport
|
|
.physical_size
|
|
.min(window_size - viewport.physical_position)
|
|
.max(UVec2::new(20, 20));
|
|
}
|
|
}
|
|
|
|
fn setup(
|
|
mut commands: Commands,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
|
window: Single<&Window>,
|
|
) {
|
|
let window_size = window.resolution.physical_size().as_vec2();
|
|
|
|
// Initialize centered, non-window-filling viewport
|
|
commands.spawn((
|
|
Camera2d,
|
|
Camera {
|
|
viewport: Some(Viewport {
|
|
physical_position: (window_size * 0.125).as_uvec2(),
|
|
physical_size: (window_size * 0.75).as_uvec2(),
|
|
..default()
|
|
}),
|
|
..default()
|
|
},
|
|
));
|
|
|
|
// Create a minimal UI explaining how to interact with the example
|
|
commands.spawn((
|
|
Text::new(
|
|
"Move the mouse to see the circle follow your cursor.\n\
|
|
Use the arrow keys to move the camera.\n\
|
|
Use the comma and period keys to zoom in and out.\n\
|
|
Use the WASD keys to move the viewport.\n\
|
|
Use the IJKL keys to resize the viewport.",
|
|
),
|
|
Node {
|
|
position_type: PositionType::Absolute,
|
|
top: Val::Px(12.0),
|
|
left: Val::Px(12.0),
|
|
..default()
|
|
},
|
|
));
|
|
|
|
// Add mesh to make camera movement visible
|
|
commands.spawn((
|
|
Mesh2d(meshes.add(Rectangle::new(40.0, 20.0))),
|
|
MeshMaterial2d(materials.add(Color::from(GREEN))),
|
|
));
|
|
|
|
// Add background to visualize viewport bounds
|
|
commands.spawn((
|
|
Mesh2d(meshes.add(Rectangle::new(50000.0, 50000.0))),
|
|
MeshMaterial2d(materials.add(Color::linear_rgb(0.01, 0.01, 0.01))),
|
|
Transform::from_translation(Vec3::new(0.0, 0.0, -200.0)),
|
|
));
|
|
}
|