Skip to content

DragonFoxCollective/bevy_event_chain

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bevy_event_chain

crates.io

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.

Sample

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);
}

Usage

[ChainEvent]

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));

[RelatedChainEvent]

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);
}

About

Easy trait-like behavior using observers and relations in Bevy

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages