aevyrie / bevy_mod_picking

Picking and pointer events for Bevy.
https://crates.io/crates/bevy_mod_picking
Apache License 2.0
764 stars 170 forks source link

Sprite Highlighting #315

Open StrikeForceZero opened 6 months ago

StrikeForceZero commented 6 months ago

After some experimentation, I discovered that this mod does not support highlighting sprites; it just picks them.

Or I'm failing to configure the mod correctly, but after reviewing the code; none of the systems seem to trigger because they all rely on Handle<ColorMaterial> when Sprites use Handle<Image>. However, the sprites color overlay is controlled by the Sprite.color field.

Should we add support for highlighting sprites?

I'd be happy to submit a PR, but this POC might be considered naive, and there's probably a better way to incorporate this.

I tried my best to maintain the expectation that it would be using ColorMaterial so the existing API to use or override the GlobalHighlight or individual Highlight is still possible.

Below is my POC if interested:

POC

can be dropped into any project as a plugin that also uses the PickingPlugin with highlighting enabled

use bevy::prelude::*;
#[cfg(feature = "egui")]
use bevy_inspector_egui::prelude::*;
use bevy_mod_picking::focus::PickingInteraction;
use bevy_mod_picking::highlight::{GlobalHighlight, Highlight, InitialHighlight, PickHighlight};
use bevy_mod_picking::picking_core::PickSet;
use bevy_mod_picking::prelude::PickSelection;

/// Automatically records the "initial" state of highlightable entities.
/// monkey patch for Sprite
fn get_sprite_initial_highlight_asset(
    mut commands: Commands,
    mut colors: ResMut<Assets<ColorMaterial>>,
    entity_asset_query: Query<(Entity, &Sprite), Added<PickHighlight>>,
    mut highlighting_query: Query<Option<&mut InitialHighlight<ColorMaterial>>>,
) {
    for (entity, sprite) in entity_asset_query.iter() {
        let color_handle = colors.add(ColorMaterial::from(sprite.color));
        commands.entity(entity).insert(color_handle.clone());
        match highlighting_query.get_mut(entity) {
            Ok(Some(mut highlighting)) => highlighting.initial = color_handle,
            _ => {
                commands.entity(entity).insert(InitialHighlight {
                    initial: color_handle,
                });
            }
        }
    }
}

/// Apply highlighting assets to entities based on their state.
/// monkey patch for Sprite
fn update_sprite_highlight(
    global_defaults: Res<GlobalHighlight<ColorMaterial>>,
    color_materials: Res<Assets<ColorMaterial>>,
    mut query: Query<
        (
            &mut Sprite,
            &PickingInteraction,
            Option<&InitialHighlight<ColorMaterial>>,
            Option<&Highlight<ColorMaterial>>,
        ),
        Changed<PickingInteraction>>,
) {
    for (mut sprite, picking_interaction, initial_highlight, highlight_override) in query.iter_mut() {
        let material_handle = match picking_interaction {
            PickingInteraction::Pressed => Some(global_defaults.pressed(&highlight_override)),
            PickingInteraction::Hovered => Some(global_defaults.hovered(&highlight_override)),
            PickingInteraction::None => initial_highlight.map_or(None, |h| Some(h.initial.to_owned())),
        };
        if let Some(material_handle) = material_handle {
            if let Some(color_material) = color_materials.get(material_handle) {
                sprite.color = color_material.color;
            }
        }
    }
}

/// If the interaction state of a selected entity is `None`, set the highlight color to `selected`.
/// monkey patch for Sprite
fn update_sprite_selection(
    global_defaults: Res<GlobalHighlight<ColorMaterial>>,
    color_materials: Res<Assets<ColorMaterial>>,
    mut interaction_query: Query<
        (
            &mut Sprite,
            &PickingInteraction,
            &PickSelection,
            &InitialHighlight<ColorMaterial>,
            Option<&Highlight<ColorMaterial>>,
        ),
        Or<(Changed<PickSelection>, Changed<PickingInteraction>)>,
    >,
) {
    for (mut sprite, interaction, selection, init_highlight, h_override) in &mut interaction_query {
        if let PickingInteraction::None = interaction {
            let material= if selection.is_selected {
                global_defaults.selected(&h_override)
            } else {
                init_highlight.initial.to_owned()
            };
            if let Some(color_material) = color_materials.get(material) {
                sprite.color = color_material.color;
            }
        }
    }
}

pub struct SubPlugin;

impl Plugin for SubPlugin {
    fn build(&self, app: &mut App) {
        app
            .add_systems(PreUpdate, (
                    get_sprite_initial_highlight_asset,
                    update_sprite_highlight,
                    update_sprite_selection,
                )
                .chain()
                .in_set(PickSet::Last)
            )
        ;
    }
}
aevyrie commented 6 months ago

The highlighting plugin was built to support any asset type. You can add a HighlightingPlugin<Image>, which will allow you to swap out an entity's Image asset based on picking state. If we want to support changing properties other than assets, we should probably make a more generic highlighting plugin that can query any component on an entity. However, at this point, it sounds like it would be much simpler to implement this with an event listener.