mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
StateTransitionEvent (#11089)
# Objective - Make it possible to react to arbitrary state changes - this will be useful regardless of the other changes to states currently being discussed ## Solution - added `StateTransitionEvent<S>` struct - previously, this would have been impossible: ```rs #[derive(States, Eq, PartialEq, Hash, Copy, Clone, Default)] enum MyState { #[default] Foo, Bar(MySubState), } enum MySubState { Spam, Eggs, } app.add_system(Update, on_enter_bar); fn on_enter_bar(trans: EventReader<StateTransition<MyState>>){ for (befoare, after) in trans.read() { match before, after { MyState::Foo, MyState::Bar(_) => info!("detected transition foo => bar"); _, _ => (); } } } ``` --- ## Changelog - Added - `StateTransitionEvent<S>` - Fired on state changes of `S` ## Migration Guide N/A no breaking changes --------- Co-authored-by: Federico Rinaldi <gisquerin@gmail.com>
This commit is contained in:
parent
8d9a0a883f
commit
1260b7bcf1
4 changed files with 33 additions and 2 deletions
|
@ -5,7 +5,7 @@ use bevy_ecs::{
|
|||
schedule::{
|
||||
apply_state_transition, common_conditions::run_once as run_once_condition,
|
||||
run_enter_schedule, InternedScheduleLabel, IntoSystemConfigs, IntoSystemSetConfigs,
|
||||
ScheduleBuildSettings, ScheduleLabel,
|
||||
ScheduleBuildSettings, ScheduleLabel, StateTransitionEvent,
|
||||
},
|
||||
};
|
||||
use bevy_utils::{intern::Interned, thiserror::Error, tracing::debug, HashMap, HashSet};
|
||||
|
@ -368,6 +368,7 @@ impl App {
|
|||
if !self.world.contains_resource::<State<S>>() {
|
||||
self.init_resource::<State<S>>()
|
||||
.init_resource::<NextState<S>>()
|
||||
.add_event::<StateTransitionEvent<S>>()
|
||||
.add_systems(
|
||||
StateTransition,
|
||||
(
|
||||
|
@ -403,6 +404,7 @@ impl App {
|
|||
pub fn insert_state<S: States>(&mut self, state: S) -> &mut Self {
|
||||
self.insert_resource(State::new(state))
|
||||
.init_resource::<NextState<S>>()
|
||||
.add_event::<StateTransitionEvent<S>>()
|
||||
.add_systems(
|
||||
StateTransition,
|
||||
(
|
||||
|
|
|
@ -40,7 +40,7 @@ pub mod prelude {
|
|||
schedule::{
|
||||
apply_deferred, apply_state_transition, common_conditions::*, Condition,
|
||||
IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfigs, NextState, OnEnter, OnExit,
|
||||
OnTransition, Schedule, Schedules, State, States, SystemSet,
|
||||
OnTransition, Schedule, Schedules, State, StateTransitionEvent, States, SystemSet,
|
||||
},
|
||||
system::{
|
||||
Commands, Deferred, In, IntoSystem, Local, NonSend, NonSendMut, ParallelCommands,
|
||||
|
|
|
@ -5,6 +5,7 @@ use std::ops::Deref;
|
|||
|
||||
use crate as bevy_ecs;
|
||||
use crate::change_detection::DetectChangesMut;
|
||||
use crate::event::Event;
|
||||
use crate::prelude::FromWorld;
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use crate::reflect::ReflectResource;
|
||||
|
@ -137,6 +138,17 @@ impl<S: States> NextState<S> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Event sent when any state transition of `S` happens.
|
||||
///
|
||||
/// If you know exactly what state you want to respond to ahead of time, consider [`OnEnter`], [`OnTransition`], or [`OnExit`]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Event)]
|
||||
pub struct StateTransitionEvent<S: States> {
|
||||
/// the state we were in before
|
||||
pub before: S,
|
||||
/// the state we're in now
|
||||
pub after: S,
|
||||
}
|
||||
|
||||
/// Run the enter schedule (if it exists) for the current state.
|
||||
pub fn run_enter_schedule<S: States>(world: &mut World) {
|
||||
world
|
||||
|
@ -146,6 +158,7 @@ pub fn run_enter_schedule<S: States>(world: &mut World) {
|
|||
|
||||
/// If a new state is queued in [`NextState<S>`], this system:
|
||||
/// - Takes the new state value from [`NextState<S>`] and updates [`State<S>`].
|
||||
/// - Sends a relevant [`StateTransitionEvent`]
|
||||
/// - Runs the [`OnExit(exited_state)`] schedule, if it exists.
|
||||
/// - Runs the [`OnTransition { from: exited_state, to: entered_state }`](OnTransition), if it exists.
|
||||
/// - Runs the [`OnEnter(entered_state)`] schedule, if it exists.
|
||||
|
@ -159,6 +172,10 @@ pub fn apply_state_transition<S: States>(world: &mut World) {
|
|||
let mut state_resource = world.resource_mut::<State<S>>();
|
||||
if *state_resource != entered {
|
||||
let exited = mem::replace(&mut state_resource.0, entered.clone());
|
||||
world.send_event(StateTransitionEvent {
|
||||
before: exited.clone(),
|
||||
after: entered.clone(),
|
||||
});
|
||||
// Try to run the schedules if they exist.
|
||||
world.try_run_schedule(OnExit(exited.clone())).ok();
|
||||
world
|
||||
|
|
|
@ -25,6 +25,7 @@ fn main() {
|
|||
Update,
|
||||
(movement, change_color).run_if(in_state(AppState::InGame)),
|
||||
)
|
||||
.add_systems(Update, log_transitions)
|
||||
.run();
|
||||
}
|
||||
|
||||
|
@ -159,3 +160,14 @@ fn change_color(time: Res<Time>, mut query: Query<&mut Sprite>) {
|
|||
.set_b((time.elapsed_seconds() * 0.5).sin() + 2.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// print when an `AppState` transition happens
|
||||
/// also serves as an example of how to use `StateTransitionEvent`
|
||||
fn log_transitions(mut transitions: EventReader<StateTransitionEvent<AppState>>) {
|
||||
for transition in transitions.read() {
|
||||
info!(
|
||||
"transition: {:?} => {:?}",
|
||||
transition.before, transition.after
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue