Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support multiple cameras #5

Merged
merged 7 commits into from
Feb 4, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 112 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,27 @@ get called when the mouse actually *moves*.
[`EventReader`]: bevy::app::EventReader

This crate aims to make this as easy as possible, by providing a
static resource that tracks the mouse position every frame.
static [resource](bevy::ecs::system::Res) that tracks the mouse position every frame.

This crate also supports more complex use cases such as multiple cameras, which are discussed further down.

## Basics

First, add the plugin to your app:

```rust
use bevy::prelude::*;
use bevy_mouse_tracking_plugin::MousePosPlugin;
fn main() {
App::build()
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(MousePosPlugin::None);
.add_plugin(MousePosPlugin::SingleCamera);
}
```

Now, you can access the resource in your [`System`]s:

[`System`]: bevy::ecs::System
[`System`]: bevy::ecs::system::System

```rust
use bevy_mouse_tracking_plugin::MousePos;
Expand All @@ -33,48 +38,134 @@ fn dbg_mouse(mouse: Res<MousePos>) {
```
...and don't forget to add the system to your app:
```rust
.add_plugin(MousePosPlugin::None)
.add_plugin(MousePosPlugin::SingleCamera)
.add_system(dbg_mouse.system());
```

This will print the screen-space location of the mouse on every frame.

However, we can do better than just screen-space: we support automatic
transformation to world-space coordinates.
Change the plugin to this:

```rust
fn main() {
App::build()
.add_plugins(DefaultPlugins)
.add_plugin(MousePosPlugin::Orthographic);
}
```
transformation to world-space coordinates via the [`MousePosWorld`] resource.

In a system...
```rust
use bevy_mouse_tracking_plugin::MousePosWorld;
fn dbg_world(mouse: Res<MousePosWorld>) {
eprintln!("{}", *mouse);
// Note: the screen-space position is still accessible
}
```

This will print the world-space location of the mouse on every frame.
Note that this is only supported for two-dimensional, orthographic camera,
but pull requests for 3D support are welcome!

Additionally, we also support a resource that tracks mouse motion, via [`MouseMotionPlugin`].
The motion can be accessed from any system in a [`MouseMotion`] [`Res`].
## Multiple cameras

You may notice that if you try to use this plugin in an app that has multiple cameras, it crashes!

```rust
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(MousePosPlugin::SingleCamera)
.add_startup_system(setup)
.run();
}
fn setup(mut commands: Commands) {
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
commands.spawn_bundle(UiCameraBundle::default());
}
```

This panics with the following output:

```
thread 'main' panicked at 'cannot identify main camera -- consider adding the MainCamera component to one of the cameras', src\mouse_pos.rs:163:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```

This is because the plugin doesn't know which of the two cameras to use when figuring out
the values of the `MousePos` and `MousePosWorld` resources. Let's take the panic message's advice.

```rust
commands.spawn_bundle(OrthographicCameraBundle::new_2d())
.insert(MainCamera); // added this line
commands.spawn_bundle(UiCameraBundle::default());
```

### Queries

[`Res`]: bevy::ecs::Res
If you want to get mouse tracking information relative to each camera individually,
simply [query](bevy::ecs::system::Query) for a `MousePos` or `MousePosWorld` as a
_component_ instead of as a resource.

```rust
fn main() {
App::new()
// plugins omitted...
.add_system(dbg_for_each);
}
fn dbg_for_each(mouse_pos: Query<&MousePosWorld>) {
for pos in mouse_pos.iter() {
// This prints the mouse position twice per frame:
// once relative to the UI camera, and once relative to the physical camera.
eprintln!("{}", *pos);
}
}
```

If you want the mouse position for a specific camera, you can add query filters as always.
Note that as of `bevy 0.6`, the only way to tell the difference between a UI camera and
an orthographic camera is by checking for the [`Frustum`] component.

[`Frustum`]: bevy::render::primitives::Frustum

```rust
use bevy::render::primitives::Frustum;
fn dbg_ui_pos(mouse_pos: Query<&MousePosWorld, Without<Frustum>>) {
// query for the UI camera, which doesn't have a Frustum component.
let pos = mouse_pos.single();
eprintln!("{}", *pos);
}
```

### No main camera

Let's say you have multiple cameras in your app, and you want to treat them all equally,
without declaring any one of them as the main camera.
Change the plugin to this:

```rust
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugin(MousePosPlugin::MultiCamera) // SingleCamera -> MultiCamera
.add_startup_system(setup)
// ...
}
```

Now, you can add as many cameras as you want, without having to worry about marking any
of them as the main camera.
Note that `MousePos` and `MousePosWorld` will no longer be accessible as global resources
-- you can only access them by `Query`ing camera entities.

## Mouse motion

This crate supports a resource that tracks mouse motion, via [`MouseMotionPlugin`].
The motion can be accessed from any system in a [`MouseMotion`] resource.

[`Res`]: bevy::ecs::system::Res

## Crate name

As a final aside: the name of this crate is intentionally verbose.
This is because I don't want to steal a crate name, especially since
This is because I didn't want to steal a crate name, especially since
it is very likely that this crate will eventually be made redundant by
future updates to `bevy`.
I recommend renaming the crate in your `Cargo.toml`:
```
[dependencies]
mouse_tracking = { package = "bevy_mouse_tracking_plugin", version = "..." }
```

License: MIT
38 changes: 12 additions & 26 deletions examples/screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,22 @@ fn main() {
.add_plugins(DefaultPlugins)
.insert_resource(ClearColor(Color::BLACK))
.insert_resource(WindowDescriptor::default())
.add_plugin(MousePosPlugin::None)
.add_plugin(MousePosPlugin::SingleCamera)
.add_startup_system(setup)
.add_system(bevy::input::system::exit_on_esc_system.system())
.add_system(run)
.run();
}

fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
window: Res<WindowDescriptor>,
) {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, window: Res<WindowDescriptor>) {
// Spawn a Camera
commands
.spawn_bundle(OrthographicCameraBundle::new_2d());
commands.spawn_bundle(OrthographicCameraBundle::new_2d());

// Reference for the origin
commands
.spawn_bundle(SpriteBundle {
texture: asset_server.load("origin.png"),
..Default::default()
});
commands.spawn_bundle(SpriteBundle {
texture: asset_server.load("origin.png"),
..Default::default()
});

// Hud
let font = asset_server.load("FiraMono-Medium.ttf");
Expand All @@ -48,10 +42,7 @@ fn setup(
horizontal: HorizontalAlign::Left,
};
let (win_width, win_height) = (window.width, window.height);
let (hud_x, hud_y) = (
win_width / 2. * -1.,
win_height / 2.,
);
let (hud_x, hud_y) = (win_width / 2. * -1., win_height / 2.);
let translation = Vec3::new(hud_x, hud_y, 0.);
let transform = Transform::from_translation(translation);
let value = "Mouse: (-, -)".to_string();
Expand All @@ -65,17 +56,12 @@ fn setup(
.insert(Hud);
}

fn run(
mouse_pos: Res<MousePos>,
mut hud_text: Query<&mut Text, With<Hud>>,
) {
let hud_value = format!("Mouse: ({}, {})",
mouse_pos.x, mouse_pos.y,
);
fn run(mouse_pos: Res<MousePos>, mut hud_text: Query<&mut Text, With<Hud>>) {
let hud_value = format!("Mouse: ({}, {})", mouse_pos.x, mouse_pos.y,);

if let Some(mut hud_text)= hud_text.iter_mut().next() {
if let Some(mut hud_text) = hud_text.iter_mut().next() {
hud_text.sections.first_mut().unwrap().value = hud_value.clone();
} else {
println!("No Hud Found");
}
}
}
35 changes: 13 additions & 22 deletions examples/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,22 @@ fn main() {
.add_plugins(DefaultPlugins)
.insert_resource(ClearColor(Color::BLACK))
.insert_resource(WindowDescriptor::default())
.add_plugin(MousePosPlugin::Orthographic)
.add_plugin(MousePosPlugin::SingleCamera)
.add_startup_system(setup)
.add_system(bevy::input::system::exit_on_esc_system.system())
.add_system(run)
.run();
}

fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
window: Res<WindowDescriptor>,
) {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, window: Res<WindowDescriptor>) {
// Spawn a Camera
commands
.spawn_bundle(OrthographicCameraBundle::new_2d());
commands.spawn_bundle(OrthographicCameraBundle::new_2d());

// Reference for the origin
commands
.spawn_bundle(SpriteBundle {
texture: asset_server.load("origin.png"),
..Default::default()
});
commands.spawn_bundle(SpriteBundle {
texture: asset_server.load("origin.png"),
..Default::default()
});

// Reference for the mouse position
commands
Expand All @@ -56,10 +50,7 @@ fn setup(
horizontal: HorizontalAlign::Left,
};
let (win_width, win_height) = (window.width, window.height);
let (hud_x, hud_y) = (
win_width / 2. * -1.,
win_height / 2.,
);
let (hud_x, hud_y) = (win_width / 2. * -1., win_height / 2.);
let translation = Vec3::new(hud_x, hud_y, 0.);
let transform = Transform::from_translation(translation);
let value = "Screen: (-, -)\nWorld: (-, -)".to_string();
Expand All @@ -79,16 +70,16 @@ fn run(
mut hud_text: Query<&mut Text, With<Hud>>,
mut cursor: Query<&mut Transform, With<Cursor>>,
) {
let hud_value = format!("Screen: ({}, {})\nWorld: ({}, {})",
mouse_screen_pos.x, mouse_screen_pos.y,
mouse_world_pos.x, mouse_world_pos.y,
let hud_value = format!(
"Screen: ({}, {})\nWorld: ({}, {})",
mouse_screen_pos.x, mouse_screen_pos.y, mouse_world_pos.x, mouse_world_pos.y,
);

if let Some(mut hud_text)= hud_text.iter_mut().next() {
if let Some(mut hud_text) = hud_text.iter_mut().next() {
hud_text.sections.first_mut().unwrap().value = hud_value.clone();
}

if let Some(mut cursor_transform) = cursor.iter_mut().next() {
cursor_transform.translation = Vec3::new(mouse_world_pos.x, mouse_world_pos.y, 0.);
}
}
}
Loading