Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
aevyrie committed Oct 27, 2023
2 parents 589a4c7 + aa0e1c9 commit 6b6cb3a
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 41 deletions.
10 changes: 6 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Highlights

- Faster compile times.
- Sprites now support scale, rotation, and anchors.
- Sprites now support atlases, scale, rotation, and anchors.
- All `egui` widgets, including side panels, are now supported.
- `bevy_mod_raycast` backend is now even simpler, no longer requiring any components to work.
- More flexible picking behavior and `bevy_ui` compatibility with the updated `Pickable` component.
Expand All @@ -21,8 +21,8 @@
- Changed: The plugin no longer respects bevy_ui's `FocusPolicy` because it was not flexible enough.
This has been replaced with new fields on the `Pickable` component. You can use this to override
the behavior of any entity in picking.
- This allows you to decide if that entity will block lower
entities (on by default), and if that entity should emit events and be hover-able (on by default).
- This allows you to decide if that entity will block lower entities (on by default), and if that
entity should emit events and be hover-able (on by default).
- To make objects non-pickable, instead of removing the `Pickable` entity, use the new const value
`Pickable::IGNORE`.
- Changed: The `PointerInteraction` component, which is added to pointers and tracks all entities
Expand All @@ -43,7 +43,8 @@

### Sprite Backend

- Added: the sprite backend now supports sprite scale, rotation, and custom anchors.
- Added: support for sprite scale, rotation, and custom anchors.
- Added: support for sprite atlases.

### `bevy_mod_raycast` Backend
- Fixed: the backend now checks render layers when filtering entities.
Expand All @@ -65,6 +66,7 @@
their ordering to ensure `PickSelection` is always updated on the frame the pointer is released.
- Fixed: removed unused `PickSet::EventListeners` and fixed (upstream) eventlisteners running in the
`Update` schedule instead of the `PreUpdate` schedule.
- Fixed: `interaction_should_run` updating the wrong `PickingPluginsSettings` field.

# 0.15.0

Expand Down
Binary file added assets/images/gabe-idle-run.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
87 changes: 53 additions & 34 deletions backends/bevy_picking_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use bevy_asset::prelude::*;
use bevy_ecs::prelude::*;
use bevy_math::prelude::*;
use bevy_render::prelude::*;
use bevy_sprite::Sprite;
use bevy_sprite::{Sprite, TextureAtlas, TextureAtlasSprite};
use bevy_transform::prelude::*;
use bevy_window::PrimaryWindow;

Expand All @@ -25,6 +25,7 @@ pub mod prelude {
/// Adds picking support for [`bevy_sprite`].
#[derive(Clone)]
pub struct SpriteBackend;

impl Plugin for SpriteBackend {
fn build(&self, app: &mut App) {
app.add_systems(PreUpdate, sprite_picking.in_set(PickSet::Backend));
Expand All @@ -37,14 +38,18 @@ pub fn sprite_picking(
cameras: Query<(Entity, &Camera, &GlobalTransform)>,
primary_window: Query<Entity, With<PrimaryWindow>>,
images: Res<Assets<Image>>,
sprite_query: Query<(
Entity,
&Sprite,
&Handle<Image>,
&GlobalTransform,
&ComputedVisibility,
Option<&Pickable>,
)>,
texture_atlas: Res<Assets<TextureAtlas>>,
sprite_query: Query<
(
Entity,
(Option<&Sprite>, Option<&TextureAtlasSprite>),
(Option<&Handle<Image>>, Option<&Handle<TextureAtlas>>),
&GlobalTransform,
Option<&Pickable>,
&ComputedVisibility,
),
Or<(With<Sprite>, With<TextureAtlasSprite>)>,
>,
mut output: EventWriter<PointerHits>,
) {
let mut sorted_sprites: Vec<_> = sprite_query.iter().collect();
Expand Down Expand Up @@ -80,35 +85,49 @@ pub fn sprite_picking(
let picks: Vec<(Entity, HitData)> = sorted_sprites
.iter()
.copied()
.filter_map(
|(entity, sprite, image, sprite_transform, visibility, sprite_focus)| {
if blocked || !visibility.is_visible() {
return None;
}
.filter(|(.., visibility)| visibility.is_visible())
.filter_map(|(entity, sprite, image, sprite_transform, pickable, ..)| {
if blocked {
return None;
}

// Hit box in sprite coordinate system
// Hit box in sprite coordinate system
let (extents, anchor) = if let Some((sprite, image)) = sprite.0.zip(image.0) {
let extents = sprite
.custom_size
.or_else(|| images.get(image).map(|f| f.size()))?;
let center = -sprite.anchor.as_vec() * extents;
let rect = Rect::from_center_half_size(center, extents / 2.0);

// Transform cursor pos to sprite coordinate system
let cursor_pos_sprite = sprite_transform
.affine()
.inverse()
.transform_point3((cursor_pos_world, 0.0).into());

let is_cursor_in_sprite = rect.contains(cursor_pos_sprite.truncate());
blocked = is_cursor_in_sprite
&& sprite_focus.map(|p| p.should_block_lower) != Some(false);

is_cursor_in_sprite.then_some((
entity,
HitData::new(cam_entity, sprite_transform.translation().z, None, None),
))
},
)
let anchor = sprite.anchor.as_vec();
(extents, anchor)
} else if let Some((sprite, atlas)) = sprite.1.zip(image.1) {
let extents = sprite.custom_size.or_else(|| {
texture_atlas
.get(atlas)
.map(|f| f.textures[sprite.index].size())
})?;
let anchor = sprite.anchor.as_vec();
(extents, anchor)
} else {
return None;
};

let center = -anchor * extents;
let rect = Rect::from_center_half_size(center, extents / 2.0);

// Transform cursor pos to sprite coordinate system
let cursor_pos_sprite = sprite_transform
.affine()
.inverse()
.transform_point3((cursor_pos_world, 0.0).into());

let is_cursor_in_sprite = rect.contains(cursor_pos_sprite.truncate());
blocked =
is_cursor_in_sprite && pickable.map(|p| p.should_block_lower) != Some(false);

is_cursor_in_sprite.then_some((
entity,
HitData::new(cam_entity, sprite_transform.translation().z, None, None),
))
})
.collect();

let order = camera.order as f32;
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_picking_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl PickingPluginsSettings {
/// Whether or not systems updating entities' [`PickingInteraction`](focus::PickingInteraction)
/// component should be running.
pub fn interaction_should_run(state: Res<Self>) -> bool {
state.enable_highlighting && state.enable
state.enable_interacting && state.enable
}
}

Expand Down
56 changes: 54 additions & 2 deletions examples/sprite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ fn main() {
DefaultPlugins.set(low_latency_window_plugin()),
DefaultPickingPlugins,
))
.add_systems(Startup, setup)
.add_systems(Update, move_sprite)
.add_systems(Startup, (setup, setup_atlas))
.add_systems(Update, (move_sprite, animate_sprite))
.run();
}

Expand Down Expand Up @@ -86,3 +86,55 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
}
});
}

#[derive(Component)]
struct AnimationIndices {
first: usize,
last: usize,
}

#[derive(Component, Deref, DerefMut)]
struct AnimationTimer(Timer);

fn animate_sprite(
time: Res<Time>,
mut query: Query<(
&AnimationIndices,
&mut AnimationTimer,
&mut TextureAtlasSprite,
)>,
) {
for (indices, mut timer, mut sprite) in &mut query {
timer.tick(time.delta());
if timer.just_finished() {
sprite.index = if sprite.index == indices.last {
indices.first
} else {
sprite.index + 1
};
}
}
}

fn setup_atlas(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
) {
let texture_handle = asset_server.load("images/gabe-idle-run.png");
let texture_atlas =
TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1, None, None);
let texture_atlas_handle = texture_atlases.add(texture_atlas);
// Use only the subset of sprites in the sheet that make up the run animation
let animation_indices = AnimationIndices { first: 1, last: 6 };
commands.spawn((
SpriteSheetBundle {
texture_atlas: texture_atlas_handle,
sprite: TextureAtlasSprite::new(animation_indices.first),
transform: Transform::from_xyz(300.0, 0.0, 0.0).with_scale(Vec3::splat(6.0)),
..default()
},
animation_indices,
AnimationTimer(Timer::from_seconds(0.1, TimerMode::Repeating)),
));
}

0 comments on commit 6b6cb3a

Please sign in to comment.