| bevy | bevy_event_chain |
|---|---|
| 0.17 | 0.1 |
Easy trait-like behavior using observers and relations.
Relationship targets ("parents") initiate a chain of events that go one-by-one through their relationships ("children") before returning to the target. This is useful for acting as one mutable event, like equipment progressively modifying stats of attacks when they happen.
pub use bevy::prelude::*;
pub use bevy_event_chain::*;
#[derive(Component)]
#[relationship_target(relationship = TestRelationshipOf)]
pub struct TestRelationships(Vec<Entity>);
#[derive(Component)]
#[relationship(relationship_target = TestRelationships)]
pub struct TestRelationshipOf(pub Entity);
#[derive(RelatedChainEvent, Clone)]
#[related_chain_event(relationship_target = TestRelationships)]
pub struct RelatedTestEvent {
pub value: i32,
chain: RelatedEventChain,
}
impl RelatedTestEvent {
pub fn new(relationship_target: Entity, relation: &ChainEventRelation<Self>) -> Self {
Self {
value: 0,
chain: relation.new_chain(relationship_target),
}
}
}
#[derive(Event)]
pub struct DoTest;
#[derive(Resource)]
pub struct ResultResource(pub i32);
fn main() {
let mut world = World::new();
let root = world.spawn_empty().id();
world.spawn(TestRelationshipOf(root)).observe(
|mut trigger: On<RelatedTestEvent>, mut commands: Commands| {
trigger.value += 1;
trigger.trigger_next(&mut commands);
},
);
world.spawn(TestRelationshipOf(root)).observe(
|mut trigger: On<RelatedTestEvent>, mut commands: Commands| {
trigger.value += 2;
trigger.trigger_next(&mut commands);
},
);
world.add_observer(
|trigger: On<RelatedTestEvent, TestRelationships>, mut commands: Commands| {
commands.insert_resource(ResultResource(trigger.value));
},
);
world.flush();
world.add_observer(
move |_do: On<DoTest>,
mut commands: Commands,
relation: ChainEventRelation<RelatedTestEvent>| {
RelatedTestEvent::new(root, &relation).trigger(&mut commands);
},
);
world.trigger(DoTest);
world.flush();
let result = world.resource::<ResultResource>().0;
assert_eq!(result, 3);
}The basic, manual version of the event chain.
Derive [ChainEvent] (and Clone)
on an event. It impls Event and EntityEvent. Similarly to
EntityEvent, it needs a field of type [EventChain]
that is either named chain or has the #[event_target] attribute.
Also similarly to EntityEvent, it uses EntityTrigger,
but this can be changed with #[chain_event(trigger = ...)].
#[derive(ChainEvent, Clone)]
pub struct TestEvent {
pub value: i32,
chain: EventChain,
}The [EventChain] is constructed from a Vec<Entity> representing the entities in the order to send the event to,
or optionally with new_with_end which just appends another entity (typically the parent or relationship target
between them) to the end of the list.
impl TestEvent {
pub fn new(targets: Vec<Entity>, root: Entity) -> Self {
Self {
value: 0,
chain: EventChain::new_with_end(targets, root),
}
}
}Spawn your entities, usually using a relationship.
let parent = commands.spawn_empty().id();
let child = commands.spawn(TestRelationshipOf(parent)).id();Observe the chain event on each of the entities. Each entity should trigger ChainEvent::next(&self) -> Self to pass the event forward.
commands.entity(child).observe(
|mut trigger: On<TestEvent>, mut commands: Commands| {
trigger.value += 1;
commands.trigger(trigger.next());
},
);Finally, consume the event with the last entity if need be.
commands.entity(root).observe(
|trigger: On<TestEvent>, mut commands: Commands| {
commands.insert_resource(ResultResource(trigger.value));
},
);You can now trigger events!
let related = relationships
.get(root)?
.collection()
.clone();
commands.trigger(TestEvent::new(related, root));The version of the event chain that's way easier to use, using relationships.
Start by creating your relationship as normal.
#[derive(Component)]
#[relationship_target(relationship = TestRelationshipOf)]
pub struct TestRelationships(Vec<Entity>);
#[derive(Component)]
#[relationship(relationship_target = TestRelationships)]
pub struct TestRelationshipOf(pub Entity);Derive [RelatedChainEvent] (and Clone) on an event. It impls Event and
EntityEvent. Similarly to EntityEvent,
it needs a field of type [RelatedEventChain] that is either named chain or has the #[event_target] attribute.
The relationship must be specified in the #[related_chain_event] attribute, using either relationship_target = ...
or relationship = ... or both. (Personally, I think relationship_target = Children looks better than relationship = ChildOf.)
The event uses EntityComponentTrigger
(NOT EntityComponentsTrigger) and can't be changed.
#[derive(RelatedChainEvent, Clone)]
#[related_chain_event(relationship_target = TestRelationships)]
pub struct RelatedTestEvent {
pub value: i32,
chain: RelatedEventChain,
}The RelatedEventChain is constructed by requesting [ChainEventRelation<MyEvent>] as a SystemParam
and calling ChainEventRelation::new_chain(relationship_target: Entity).
impl RelatedTestEvent {
pub fn new(relationship_target: Entity, relation: &ChainEventRelation<Self>) -> Self {
Self {
value: 0,
chain: relation.new_chain(relationship_target),
}
}
}Spawn your entities and their relationship.
let parent = commands.spawn_empty().id();
let child = commands.spawn(TestRelationshipOf(parent)).id();Observe the chain event on each of the entities. Each entity should trigger RelatedChainEvent::next(&self) -> Self
and, from the next event, RelatedChainEvent::get_trigger(&self) -> EntityComponentTrigger to pass the event forward.
For simplicity, you can also use RelatedChainEvent::trigger(self, commands: &mut Commands) from the next event or
RelatedChainEvent::trigger_next(&self, commands: &mut Commands) (which just triggers
next() and next.get_trigger())
from the source event.
commands.entity(child).observe(
|mut trigger: On<RelatedTestEvent>, mut commands: Commands| {
trigger.value += 1;
trigger.trigger_next(&mut commands);
},
);Finally, consume the event with the relationship target if need be.
commands.entity(root).observe(
|trigger: On<RelatedTestEvent>, mut commands: Commands| {
commands.insert_resource(ResultResource(trigger.value));
},
);Also for simplicity, the relationship type and its target type are passed into the event trigger
(hence the EntityComponentTrigger),
so you can specify a type into the On to use a global observer instead. This is useful for globally consuming the final output of
an event, or for using a global observer to pass along an event that should always be passed along and shouldn't be mutated (eg. if it
only contains entity references but not their mutable components), so you don't have to worry about passing it along in each of the
entity observers.
app.add_observer(
|trigger: On<RelatedTestEvent, TestRelationships>, mut commands: Commands| {
commands.insert_resource(ResultResource(trigger.value));
},
);You can now trigger events!
fn fire_event(
relation: ChainEventRelation<RelatedTestEvent>,
...
) {
...
RelatedTestEvent::new(relationship_target, &relation).trigger(&mut commands);
}