Best practices for systems which must operate on non-exclusive lists of components? #5241
-
I am trying to set up a damage system in Bevy. There is a Naturally, the player has a health component, but doesn't need a damage component. Projectiles like lasers need a damage component, but don't need a health component. There's a simple system we can use for these purposes: fn damage_system(mut health: Query<&mut Health, Without<Damage>>, mut damage: Query<&mut Damage, Without<Health>>) The problem comes when we start to think about the types of components we could have on more complicated entities. It is common to have enemies deal contact damage, for example. Presumably these would need both damage and health components. We'll call such entities "contacts" for the purposes of brevity. We now need to check the following:
(Option 3 can be ignored depending on the design of your game, but for generality I'll include it.) In order to cover all of these possibilities, while respecting the rules of queries needing to not overlap, we can accomplish this with three systems total which essentially do exactly the same thing. (In theory we'd need four including the above, but we can fold that into Option 2): fn contact_health_system(mut health: Query<&mut Health, Without<Damage>>, mut contacts: Query<&Damage, With<Health>>)
{
//Handles Option 1
}
fn damage_system(mut health: Query<&mut Health>, mut damage: Query<&mut Damage, Without<Health>>)
{
//Handles the first case and Option 2
}
fn contacts_system(mut health: Query<(&mut Health, &mut Damage)>)
{
//Use .iter_combinations to handle Option 3
} While this works, it definitely feels quite inelegant. It also scales badly if we include more optional components which are interacting. In looking for solutions, I found the Does anyone have advice or tips on better ways to achieve this functionality, or is this probably the best way to go? I recognize that ownership and mutability rules might just be in the way here somewhat unavoidably. The best solution I've been able to imagine is adding a third marker component, getting a query of all marked entities, then using |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 3 replies
-
I came across the same issues myself. I've solved this, plus the issue of code duplication, using events. pub struct DamageEvent {
pub damage: f32,
pub target: Entity,
}
pub fn process_damage_events(
mut events: EventReader<DamageEvent>,
mut health_query: Query<&mut Health>,
) {
for &DamageEvent { damage, target } in events.iter() {
// If this entity has a Health component, damage it.
if let Ok(mut health) = health_query.get_mut(target) {
health -= damage;
}
}
}
fn contact_damage(
mut events: EventWriter<DamageEvent>,
query: Query<(&ContactDamage, &CollidingEntities)>,
) {
for (contact_damage, collisions) in query.iter() {
for target in collisions {
events.send(DamageEvent {
damage: contact_damage.damage,
target,
});
}
}
} Now, any system that deals damage in some way can simply send a |
Beta Was this translation helpful? Give feedback.
-
An alternate method I've found (marking the other answer as correct as I think it is the best for most users not doing weird stuff like me), is using The Bevy Cheatbook has a great chapter on them for those interested: https://bevy-cheatbook.github.io/features/parent-child.html The downside of this is potentially having duplicate copies of our colliders when the hit and hurt boxes for a contact are the same, but it also has upside that they are different by default, so there's no extra code needed to accommodate two different shapes for the same entity. |
Beta Was this translation helpful? Give feedback.
I came across the same issues myself. I've solved this, plus the issue of code duplication, using events.