|
use std::collections::HashSet; |
|
|
|
use bevy::{ |
|
core::Name, |
|
ecs::{ |
|
component::Component, |
|
entity::Entity, |
|
event::EventReader, |
|
query::{ Added, With }, |
|
system::{ Commands, Query }, |
|
}, |
|
hierarchy::{ Parent, PushChild }, |
|
log, |
|
math::Vec2, |
|
transform::components::GlobalTransform, |
|
}; |
|
use bevy_ecs_ldtk::{ ldtk::ldtk_fields::LdtkFields, EntityInstance }; |
|
use bevy_rapier2d::{ geometry::{ ActiveEvents, Collider, Sensor }, pipeline::CollisionEvent }; |
|
|
|
use crate::entities::Player; |
|
use crate::plugins::rapier_utils::reciprocal_collisions; |
|
|
|
#[derive(Component, Default)] |
|
pub struct InteractionSensor { |
|
pub intersecting_entities: HashSet<Entity>, |
|
pub closest_entity: Option<Entity>, |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
pub(crate) fn spawn_interaction_sensor( |
|
mut commands: Commands, |
|
mut detect_interaction_for: Query<(Entity, &Collider), Added<Player>> |
|
) { |
|
for (parent, shape) in &mut detect_interaction_for { |
|
if let Some(cuboid) = shape.as_cuboid() { |
|
let Vec2 { x: half_extents_x, y: half_extents_y } = cuboid.half_extents(); |
|
let mut sensor_cmds = commands.spawn(( |
|
ActiveEvents::COLLISION_EVENTS, |
|
Collider::cuboid(half_extents_x * 4.0, half_extents_y * 1.5), |
|
Sensor, |
|
InteractionSensor::default(), |
|
)); |
|
#[cfg(feature = "dev_features")] |
|
sensor_cmds.insert(( |
|
Name::new("interaction_sensor"), |
|
bevy_rapier2d::render::ColliderDebugColor(bevy::color::palettes::css::GREEN.into()), |
|
)); |
|
let child = sensor_cmds.id(); |
|
commands.add(PushChild { parent, child }); |
|
} |
|
} |
|
} |
|
|
|
#[derive(Component)] |
|
pub struct Interactive { |
|
pub name: String, |
|
} |
|
|
|
|
|
|
|
pub(crate) fn setup_interactive_entity( |
|
mut commands: Commands, |
|
query: Query<(Entity, &EntityInstance), Added<EntityInstance>> |
|
) { |
|
for (entity, ldtk_entity) in query.iter() { |
|
if |
|
let (Ok(name), Ok(true)) = ( |
|
ldtk_entity.get_string_field("name"), |
|
ldtk_entity.get_bool_field("hasDialogue"), |
|
) |
|
{ |
|
log::debug!("New interactive {}: {}", ldtk_entity.identifier, name); |
|
commands.entity(entity).insert(Interactive { name: name.into() }); |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
pub(crate) fn interaction_detection( |
|
mut interaction_sensors: Query<&mut InteractionSensor>, |
|
interactive_entities: Query<Entity, With<Interactive>>, |
|
mut collisions: EventReader<CollisionEvent> |
|
) { |
|
reciprocal_collisions(&mut collisions, move |interactor_entity, interactive_entity, _, start| { |
|
if |
|
let (Ok(mut interactor), true) = ( |
|
interaction_sensors.get_mut(*interactor_entity), |
|
interactive_entities.contains(*interactive_entity), |
|
) |
|
{ |
|
let set = &mut interactor.intersecting_entities; |
|
if start { |
|
set.insert(*interactive_entity); |
|
} else { |
|
set.remove(interactive_entity); |
|
} |
|
true |
|
} else { |
|
false |
|
} |
|
}); |
|
} |
|
|
|
|
|
|
|
pub(crate) fn update_interactions( |
|
mut interaction_sensors: Query<(&mut InteractionSensor, &Parent)>, |
|
player_query: Query<&GlobalTransform, With<Player>>, |
|
interactive: Query<(&GlobalTransform, &Collider), With<Interactive>> |
|
) { |
|
for (mut sensor, parent) in &mut interaction_sensors { |
|
|
|
|
|
if sensor.intersecting_entities.is_empty() { |
|
if sensor.closest_entity != None { |
|
sensor.closest_entity = None; |
|
} |
|
continue; |
|
} |
|
let player_transform = player_query.get(**parent).unwrap(); |
|
|
|
let mut closest_dist = f32::INFINITY; |
|
let mut closest_entity = None; |
|
for interactive_entity in &sensor.intersecting_entities { |
|
let Ok((interactive_transform, interactive_collider)) = interactive.get( |
|
*interactive_entity |
|
) else { |
|
continue; |
|
}; |
|
|
|
let distance = interactive_collider.distance_to_local_point( |
|
player_transform.reparented_to(interactive_transform).translation.truncate(), |
|
false |
|
); |
|
if distance < closest_dist { |
|
closest_dist = distance; |
|
closest_entity = Some(*interactive_entity); |
|
} |
|
} |
|
|
|
|
|
if sensor.closest_entity != closest_entity { |
|
sensor.closest_entity = closest_entity; |
|
} |
|
} |
|
} |
|
|