mirror of
https://github.com/bevyengine/bevy
synced 2024-12-26 13:03:06 +00:00
ec1aa48fc6
# Objective - Often in games you will want to create chains of systems that modify some event. For example, a chain of damage systems that handle a DamageEvent and modify the underlying value before the health system finally consumes the event. Right now this requires either: * Using a component added to the entity * Consuming and refiring events Neither is ideal when really all we want to do is read the events value, modify it, and write it back. ## Solution - Create an EventMutator class similar to EventReader but with ResMut<T> and iterators that return &mut so that events can be mutated. ## Testing - I replicated all the existing tests for EventReader to make sure behavior was the same (I believe) and added a number of tests specific to testing that 1) events can actually be mutated, and that 2) EventReader sees changes from EventMutator for events it hasn't already seen. ## Migration Guide Users currently using `ManualEventReader` should use `EventCursor` instead. `ManualEventReader` will be removed in Bevy 0.16. Additionally, `Events::get_reader` has been replaced by `Events::get_cursor`. Users currently directly accessing the `Events` resource for mutation should move to `EventMutator` if possible. --------- Co-authored-by: poopy <gonesbird@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
144 lines
5.5 KiB
Rust
144 lines
5.5 KiB
Rust
//! This example shows how to send, mutate, and receive, events. As well as showing
|
|
//! how to you might control system ordering so that events are processed in a specific order.
|
|
//! It does this by simulating a damage over time effect that you might find in a game.
|
|
|
|
use bevy::prelude::*;
|
|
|
|
// In order to send or receive events first you must define them
|
|
// This event should be sent when something attempts to deal damage to another entity.
|
|
#[derive(Event, Debug)]
|
|
struct DealDamage {
|
|
pub amount: i32,
|
|
}
|
|
|
|
// This event should be sent when an entity receives damage.
|
|
#[derive(Event, Debug, Default)]
|
|
struct DamageReceived;
|
|
|
|
// This event should be sent when an entity blocks damage with armor.
|
|
#[derive(Event, Debug, Default)]
|
|
struct ArmorBlockedDamage;
|
|
|
|
// This resource represents a timer used to determine when to deal damage
|
|
// By default it repeats once per second
|
|
#[derive(Resource, Deref, DerefMut)]
|
|
struct DamageTimer(pub Timer);
|
|
|
|
impl Default for DamageTimer {
|
|
fn default() -> Self {
|
|
DamageTimer(Timer::from_seconds(1.0, TimerMode::Repeating))
|
|
}
|
|
}
|
|
|
|
// Next we define systems that send, mutate, and receive events
|
|
// This system reads 'DamageTimer', updates it, then sends a 'DealDamage' event
|
|
// if the timer has finished.
|
|
//
|
|
// Events are sent using an 'EventWriter<T>' by calling 'send' or 'send_default'.
|
|
// The 'send_default' method will send the event with the default value if the event
|
|
// has a 'Default' implementation.
|
|
fn deal_damage_over_time(
|
|
time: Res<Time>,
|
|
mut state: ResMut<DamageTimer>,
|
|
mut events: EventWriter<DealDamage>,
|
|
) {
|
|
if state.tick(time.delta()).finished() {
|
|
// Events can be sent with 'send' and constructed just like any other object.
|
|
events.send(DealDamage { amount: 10 });
|
|
}
|
|
}
|
|
|
|
// This system mutates the 'DealDamage' events to apply some armor value
|
|
// It also sends an 'ArmorBlockedDamage' event if the value of 'DealDamage' is zero
|
|
//
|
|
// Events are mutated using an 'EventMutator<T>' by calling 'read'. This returns an iterator
|
|
// over all the &mut T that this system has not read yet. Note, you can have multiple
|
|
// 'EventReader', 'EventWriter', and 'EventMutator' in a given system, as long as the types (T) are different.
|
|
fn apply_armor_to_damage(
|
|
mut dmg_events: EventMutator<DealDamage>,
|
|
mut armor_events: EventWriter<ArmorBlockedDamage>,
|
|
) {
|
|
for event in dmg_events.read() {
|
|
event.amount -= 1;
|
|
if event.amount <= 0 {
|
|
// Zero-sized events can also be sent with 'send'
|
|
armor_events.send(ArmorBlockedDamage);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This system reads 'DealDamage' events and sends 'DamageReceived' if the amount is non-zero
|
|
//
|
|
// Events are read using an 'EventReader<T>' by calling 'read'. This returns an iterator over all the &T
|
|
// that this system has not read yet, and must be 'mut' in order to track which events have been read.
|
|
// Again, note you can have multiple 'EventReader', 'EventWriter', and 'EventMutator' in a given system,
|
|
// as long as the types (T) are different.
|
|
fn apply_damage_to_health(
|
|
mut dmg_events: EventReader<DealDamage>,
|
|
mut rcvd_events: EventWriter<DamageReceived>,
|
|
) {
|
|
for event in dmg_events.read() {
|
|
info!("Applying {} damage", event.amount);
|
|
if event.amount > 0 {
|
|
// Events with a 'Default' implementation can be sent with 'send_default'
|
|
rcvd_events.send_default();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finally these two systems read 'DamageReceived' events.
|
|
//
|
|
// The first system will play a sound.
|
|
// The second system will spawn a particle effect.
|
|
//
|
|
// As before, events are read using an 'EventReader' by calling 'read'. This returns an iterator over all the &T
|
|
// that this system has not read yet.
|
|
fn play_damage_received_sound(mut dmg_events: EventReader<DamageReceived>) {
|
|
for _ in dmg_events.read() {
|
|
info!("Playing a sound.");
|
|
}
|
|
}
|
|
|
|
// Note that both systems receive the same 'DamageReceived' events. Any number of systems can
|
|
// receive the same event type.
|
|
fn play_damage_received_particle_effect(mut dmg_events: EventReader<DamageReceived>) {
|
|
for _ in dmg_events.read() {
|
|
info!("Playing particle effect.");
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
// Events must be added to the app before they can be used
|
|
// using the 'add_event' method
|
|
.add_event::<DealDamage>()
|
|
.add_event::<ArmorBlockedDamage>()
|
|
.add_event::<DamageReceived>()
|
|
.init_resource::<DamageTimer>()
|
|
// As always we must add our systems to the apps schedule.
|
|
// Here we add our systems to the schedule using 'chain()' so that they run in order
|
|
// This ensures that 'apply_armor_to_damage' runs before 'apply_damage_to_health'
|
|
// It also ensures that 'EventWriters' are used before the associated 'EventReaders'
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
deal_damage_over_time,
|
|
apply_armor_to_damage,
|
|
apply_damage_to_health,
|
|
)
|
|
.chain(),
|
|
)
|
|
// These two systems are not guaranteed to run in order, nor are they guaranteed to run
|
|
// after the above chain. They may even run in parallel with each other.
|
|
// This means they may have a one frame delay in processing events compared to the above chain
|
|
// In some instances this is fine. In other cases it can be an issue. See the docs for more information
|
|
.add_systems(
|
|
Update,
|
|
(
|
|
play_damage_received_sound,
|
|
play_damage_received_particle_effect,
|
|
),
|
|
)
|
|
.run();
|
|
}
|