separated / src /components /interactions.rs
Gaeros's picture
build you shall
dda7d52
raw
history blame contribute delete
No virus
5.39 kB
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>,
}
/// Spawn a sensing region around the Player.
///
/// The sensor is spawn as a children to the Player. It contains an
/// [`InteractionSensor`] component that tracks interactive entities in range
/// and the closest one.
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,
}
/// Adds the [`Interactive`] component to LDtk entities that have a name and the
/// `hasDialogue` field set to true
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() });
}
}
}
/// System collecting collision events of the interaction sensor with
/// interactive entities
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
}
});
}
/// System that tracks distances between interactive entities and the sensor, in
/// order to elect the closest interactive entity.
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 {
// Bypass the player transform query if the is no interactive entities
// in range
if sensor.intersecting_entities.is_empty() {
if sensor.closest_entity != None {
sensor.closest_entity = None;
}
continue;
}
let player_transform = player_query.get(**parent).unwrap();
// Find the closest entity.
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);
}
}
// Gate the mutation such that only real change are detected when using
// `Changed<InteractionSensor>` (See `Mut<>` mutation detection)
if sensor.closest_entity != closest_entity {
sensor.closest_entity = closest_entity;
}
}
}