Thu Nov 21 12:17:08 PM EST 2024

This commit is contained in:
Silas Bartha 2024-11-21 12:17:08 -05:00
parent 85c8056166
commit 57827d5371
Signed by: soaos
GPG Key ID: 9BD3DCC0D56A09B2
2 changed files with 52 additions and 12 deletions

View File

@ -6,7 +6,7 @@ use bevy::{prelude::*, utils::HashSet};
/// ///
/// An entity with an `Interactor` component can be passed to an `InteractorFiredEvent` in order to /// An entity with an `Interactor` component can be passed to an `InteractorFiredEvent` in order to
/// start an interaction with any nearby `Interactable` entities. /// start an interaction with any nearby `Interactable` entities.
#[derive(Component, Default)] #[derive(Component, Default, Debug)]
pub struct Interactor { pub struct Interactor {
/// All `Interactable` targets in-range of this interactor. /// All `Interactable` targets in-range of this interactor.
pub targets: HashSet<Entity>, pub targets: HashSet<Entity>,
@ -18,10 +18,19 @@ pub struct Interactor {
/// ///
/// An entity with an `Interactable` component might get passed to an `InteractionEvent` when an /// An entity with an `Interactable` component might get passed to an `InteractionEvent` when an
/// `Interactor` requests an interaction, if the interactable is in range. /// `Interactor` requests an interaction, if the interactable is in range.
#[derive(Component)] #[derive(Component, Clone, Debug)]
pub struct Interactable { pub struct Interactable {
/// An optional name for this interactable
pub name: Option<String>,
/// An optional description of the action
pub description: Option<String>,
/// Predicate to check to see if interaction is possible
pub predicate: Option<fn(Entity, &mut World) -> bool>,
pub(crate) exclusive: bool, pub(crate) exclusive: bool,
pub(crate) max_distance_squared: f32, pub(crate) max_distance_squared: f32,
pub(crate) possible: bool,
/// Whether this pickup is enabled
pub enabled: bool,
} }
impl Interactable { impl Interactable {
@ -30,17 +39,27 @@ impl Interactable {
/// If exclusive, this interactable will only be interacted with if it's the closest one to the /// If exclusive, this interactable will only be interacted with if it's the closest one to the
/// interactor, and the interaction will *not* be processed for any other in-range /// interactor, and the interaction will *not* be processed for any other in-range
/// interactables. /// interactables.
pub fn new(max_distance: f32, exclusive: bool) -> Self { pub fn new(max_distance: f32, exclusive: bool, name: Option<String>, description: Option<String>, predicate: Option<fn(Entity, &mut World) -> bool>) -> Self {
Self { Self {
name,
description,
predicate,
exclusive, exclusive,
max_distance_squared: max_distance * max_distance, max_distance_squared: max_distance * max_distance,
possible: true,
enabled: true,
} }
} }
/// Gets whether this interaction is currently possible. Set this value using predicate.
pub fn possible(&self) -> bool {
self.possible
}
} }
impl Default for Interactable { impl Default for Interactable {
fn default() -> Self { fn default() -> Self {
Self::new(1.0, false) Self::new(1.0, false, None, None, None)
} }
} }

View File

@ -26,7 +26,10 @@ pub struct InteractionPlugin;
impl Plugin for InteractionPlugin { impl Plugin for InteractionPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Update, (handle_interactor_events, update_interactor_targets)) app.add_systems(
Update,
(handle_interactor_events, update_interactor_targets, update_interactable_predicates),
)
.add_event::<InteractorFiredEvent>() .add_event::<InteractorFiredEvent>()
.add_event::<InteractionEvent>(); .add_event::<InteractionEvent>();
} }
@ -43,7 +46,7 @@ fn handle_interactor_events(
if let Some(interactable_entity) = interactor.closest { if let Some(interactable_entity) = interactor.closest {
let interactable = interactable_query.get(interactable_entity).unwrap(); let interactable = interactable_query.get(interactable_entity).unwrap();
if interactable.exclusive { if interactable.exclusive && interactable.possible {
event_writer.send(InteractionEvent { event_writer.send(InteractionEvent {
interactor: *interactor_entity, interactor: *interactor_entity,
interactable: interactable_entity, interactable: interactable_entity,
@ -52,7 +55,7 @@ fn handle_interactor_events(
} else { } else {
for interactable_entity in &interactor.targets { for interactable_entity in &interactor.targets {
let interactable = interactable_query.get(*interactable_entity).unwrap(); let interactable = interactable_query.get(*interactable_entity).unwrap();
if !interactable.exclusive { if !interactable.exclusive && interactable.possible {
event_writer.send(InteractionEvent { event_writer.send(InteractionEvent {
interactor: *interactor_entity, interactor: *interactor_entity,
interactable: *interactable_entity, interactable: *interactable_entity,
@ -73,7 +76,11 @@ fn update_interactor_targets(
let interactor_transform = transform_query.get(interactor_entity).unwrap(); let interactor_transform = transform_query.get(interactor_entity).unwrap();
let mut closest_active_interactable: Option<(f32, Entity)> = None; let mut closest_active_interactable: Option<(f32, Entity)> = None;
interactor.targets.clear();
for (interactable_entity, interactable) in interactable_query.iter_mut() { for (interactable_entity, interactable) in interactable_query.iter_mut() {
if !interactable.enabled {
continue;
}
let interactable_transform = transform_query.get(interactable_entity).unwrap(); let interactable_transform = transform_query.get(interactable_entity).unwrap();
let interactable_distance_squared = interactable_transform let interactable_distance_squared = interactable_transform
.translation() .translation()
@ -85,7 +92,7 @@ fn update_interactor_targets(
), ),
); );
if interactable_distance_squared < interactable.max_distance_squared if interactable_distance_squared < interactable.max_distance_squared
&& interactable_arccosine < PI / 8.0 && interactable_arccosine < PI / 4.0
{ {
interactor.targets.insert(interactable_entity); interactor.targets.insert(interactable_entity);
if let Some((arccosine, _)) = closest_active_interactable { if let Some((arccosine, _)) = closest_active_interactable {
@ -97,8 +104,6 @@ fn update_interactor_targets(
closest_active_interactable = closest_active_interactable =
Some((interactable_arccosine, interactable_entity)); Some((interactable_arccosine, interactable_entity));
} }
} else {
interactor.targets.remove(&interactable_entity);
} }
} }
interactor.closest = if let Some((_, interactable_entity)) = closest_active_interactable { interactor.closest = if let Some((_, interactable_entity)) = closest_active_interactable {
@ -108,3 +113,19 @@ fn update_interactor_targets(
} }
} }
} }
fn update_interactable_predicates(world: &mut World) {
let mut interactable_query = world.query::<(Entity, &mut Interactable)>();
let mut interactables = vec![];
for (interactable_entity, interactable) in interactable_query.iter(world) {
interactables.push((interactable_entity, (*interactable).clone()));
}
for (interactable_entity, interactable) in interactables.iter_mut() {
if let Some(predicate) = &interactable.predicate {
interactable.possible = predicate(*interactable_entity, world);
}
}
for ((_, mut interactable), (_, temp)) in interactable_query.iter_mut(world).zip(interactables.into_iter()) {
*interactable = temp;
}
}