diff --git a/assets/animation_graphs/toplevel.animgraph.ron b/assets/animation_graphs/toplevel.animgraph.ron index 3241d6e..de7f54e 100644 --- a/assets/animation_graphs/toplevel.animgraph.ron +++ b/assets/animation_graphs/toplevel.animgraph.ron @@ -15,9 +15,7 @@ "user events": EventQueue(( events: [ ( - event: ( - id: "", - ), + event: StringId(""), weight: 1.0, percentage: 1.0, ), @@ -37,4 +35,4 @@ input_position: (237.51926, 497.61542), output_position: (727.0, 463.0), ), -) \ No newline at end of file +) diff --git a/assets/animation_graphs/walk_to_run.animgraph.ron b/assets/animation_graphs/walk_to_run.animgraph.ron index cb74ed6..0e5838e 100644 --- a/assets/animation_graphs/walk_to_run.animgraph.ron +++ b/assets/animation_graphs/walk_to_run.animgraph.ron @@ -2,9 +2,7 @@ nodes: [ ( name: "Done", - node: FireEvent(( - id: "end_transition", - )), + node: FireEvent(EndTransition), ), ( name: "Blend", diff --git a/assets/fsm/locomotion.fsm.ron b/assets/fsm/locomotion.fsm.ron index 0d5e2fa..de8413e 100644 --- a/assets/fsm/locomotion.fsm.ron +++ b/assets/fsm/locomotion.fsm.ron @@ -3,22 +3,27 @@ ( id: "walk", graph: "animation_graphs/walk.animgraph.ron", + global_transition: Some(( + duration: 1.0, + graph: "animation_graphs/walk_to_run.animgraph.ron", + )), ), ( id: "run", graph: "animation_graphs/run.animgraph.ron", + global_transition: None, ), ], transitions: [ ( - id: "slow_down", + id: Direct("slow_down"), source: "run", target: "walk", duration: 1.0, graph: "animation_graphs/walk_to_run.animgraph.ron", ), ( - id: "speed_up", + id: Direct("speed_up"), source: "walk", target: "run", duration: 1.0, @@ -31,8 +36,8 @@ }, extra: ( states: { - "walk": (334.10522, 310.10526), - "run": (554.1376, 309.71655), + "walk": (334.10522, 309.52664), + "run": (552.15, 310.6519), }, ), ) diff --git a/crates/bevy_animation_graph/src/core/context/graph_context.rs b/crates/bevy_animation_graph/src/core/context/graph_context.rs index bce198c..509e14a 100644 --- a/crates/bevy_animation_graph/src/core/context/graph_context.rs +++ b/crates/bevy_animation_graph/src/core/context/graph_context.rs @@ -4,7 +4,7 @@ use crate::{ duration_data::DurationData, pose::Pose, prelude::AnimationGraph, - state_machine::FSMState, + state_machine::low_level::FSMState, }, prelude::DataValue, }; diff --git a/crates/bevy_animation_graph/src/core/context/graph_context_arena.rs b/crates/bevy_animation_graph/src/core/context/graph_context_arena.rs index b9000f9..ce084f9 100644 --- a/crates/bevy_animation_graph/src/core/context/graph_context_arena.rs +++ b/crates/bevy_animation_graph/src/core/context/graph_context_arena.rs @@ -1,5 +1,7 @@ use super::GraphContext; -use crate::core::{animation_graph::NodeId, prelude::AnimationGraph, state_machine::StateId}; +use crate::core::{ + animation_graph::NodeId, prelude::AnimationGraph, state_machine::low_level::LowLevelStateId, +}; use bevy::{asset::AssetId, reflect::Reflect, utils::HashMap}; #[derive(Reflect, Clone, Copy, Debug, Eq, PartialEq, Hash, Default)] @@ -9,7 +11,7 @@ pub struct GraphContextId(usize); pub struct SubContextId { pub ctx_id: GraphContextId, pub node_id: NodeId, - pub state_id: Option, + pub state_id: Option, } #[derive(Reflect, Debug)] diff --git a/crates/bevy_animation_graph/src/core/context/pass_context.rs b/crates/bevy_animation_graph/src/core/context/pass_context.rs index 9cac2b3..6962e15 100644 --- a/crates/bevy_animation_graph/src/core/context/pass_context.rs +++ b/crates/bevy_animation_graph/src/core/context/pass_context.rs @@ -10,7 +10,7 @@ use crate::{ duration_data::DurationData, errors::GraphError, pose::BoneId, - state_machine::{LowLevelStateMachine, StateId}, + state_machine::low_level::{LowLevelStateId, LowLevelStateMachine}, }, prelude::{AnimationGraph, DataValue}, }; @@ -40,11 +40,11 @@ pub struct FsmContext<'a> { #[derive(Clone)] pub struct StateStack { - pub stack: Vec<(StateId, StateRole)>, + pub stack: Vec<(LowLevelStateId, StateRole)>, } impl StateStack { - pub fn last_state(&self) -> StateId { + pub fn last_state(&self) -> LowLevelStateId { self.stack.last().unwrap().0.clone() } pub fn last_role(&self) -> StateRole { @@ -278,7 +278,7 @@ impl<'a> PassContext<'a> { pub fn str_ctx_stack(&self) -> String { let mut s = if let Some(fsm_ctx) = &self.fsm_context { format!( - "- [FSM] {}: {}", + "- [FSM] {}: {:?}", "state_id", fsm_ctx.state_stack.last_state() ) diff --git a/crates/bevy_animation_graph/src/core/edge_data/events.rs b/crates/bevy_animation_graph/src/core/edge_data/events.rs index 1f322d4..c20a3db 100644 --- a/crates/bevy_animation_graph/src/core/edge_data/events.rs +++ b/crates/bevy_animation_graph/src/core/edge_data/events.rs @@ -1,11 +1,27 @@ use bevy::reflect::{std_traits::ReflectDefault, Reflect}; use serde::{Deserialize, Serialize}; +use crate::core::state_machine::high_level::{StateId, TransitionId}; + /// Event data -#[derive(Clone, Debug, Reflect, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, Reflect, Serialize, Deserialize)] #[reflect(Default)] -pub struct AnimationEvent { - pub id: String, +pub enum AnimationEvent { + /// Trigger the most specific transition from transitioning into the provided state. That + /// will be: + /// * A direct transition, if present, or + /// * A global transition, if present + TransitionToState(StateId), + /// Trigger a specific transition (if possible) + Transition(TransitionId), + EndTransition, + StringId(String), +} + +impl Default for AnimationEvent { + fn default() -> Self { + Self::StringId("".to_string()) + } } /// Structure containing a sampled event and relevant metadata diff --git a/crates/bevy_animation_graph/src/core/plugin.rs b/crates/bevy_animation_graph/src/core/plugin.rs index 9ad024d..5c35ab8 100644 --- a/crates/bevy_animation_graph/src/core/plugin.rs +++ b/crates/bevy_animation_graph/src/core/plugin.rs @@ -4,6 +4,7 @@ use super::pose::Pose; use super::prelude::GraphClip; use super::skeleton::loader::SkeletonLoader; use super::skeleton::Skeleton; +use super::state_machine::high_level::GlobalTransition; use super::systems::apply_animation_to_targets; use super::{ animated_scene::{ @@ -89,6 +90,7 @@ impl AnimationGraphPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() .register_type::<()>() .register_type_data::<(), ReflectDefault>() // --- Node registrations diff --git a/crates/bevy_animation_graph/src/core/state_machine/common.rs b/crates/bevy_animation_graph/src/core/state_machine/common.rs deleted file mode 100644 index 48de2c8..0000000 --- a/crates/bevy_animation_graph/src/core/state_machine/common.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub type StateId = String; -pub type TransitionId = String; diff --git a/crates/bevy_animation_graph/src/core/state_machine/high_level/loader.rs b/crates/bevy_animation_graph/src/core/state_machine/high_level/loader.rs index 7628a20..29d15f9 100644 --- a/crates/bevy_animation_graph/src/core/state_machine/high_level/loader.rs +++ b/crates/bevy_animation_graph/src/core/state_machine/high_level/loader.rs @@ -1,4 +1,4 @@ -use super::{serial::StateMachineSerial, State, StateMachine, Transition}; +use super::{serial::StateMachineSerial, GlobalTransition, State, StateMachine, Transition}; use crate::core::errors::AssetLoaderError; use bevy::asset::{io::Reader, AssetLoader, AsyncReadExt, LoadContext}; @@ -26,9 +26,15 @@ impl AssetLoader for StateMachineLoader { }; for state_serial in serial.states { + let global_transition_data = + state_serial.global_transition.map(|gt| GlobalTransition { + duration: gt.duration, + graph: load_context.load(gt.graph), + }); fsm.add_state(State { id: state_serial.id, graph: load_context.load(state_serial.graph), + global_transition: global_transition_data, }); } diff --git a/crates/bevy_animation_graph/src/core/state_machine/high_level/mod.rs b/crates/bevy_animation_graph/src/core/state_machine/high_level/mod.rs index b8daee4..803dbf3 100644 --- a/crates/bevy_animation_graph/src/core/state_machine/high_level/mod.rs +++ b/crates/bevy_animation_graph/src/core/state_machine/high_level/mod.rs @@ -1,7 +1,10 @@ pub mod loader; pub mod serial; -use super::{LowLevelStateMachine, StateId, TransitionId}; +use super::low_level::{ + LowLevelState, LowLevelStateId, LowLevelStateMachine, LowLevelTransition, LowLevelTransitionId, + LowLevelTransitionType, +}; use crate::core::{ animation_graph::{AnimationGraph, PinMap}, edge_data::DataValue, @@ -10,16 +13,43 @@ use crate::core::{ use bevy::{ asset::{Asset, Handle}, math::Vec2, + prelude::ReflectDefault, reflect::Reflect, utils::HashMap, }; use serde::{Deserialize, Serialize}; +/// Unique within a high-level FSM +pub type StateId = String; + +/// Unique within a high-level FSM +#[derive(Reflect, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum TransitionId { + /// Direct transitions are still indexed by String Id + Direct(String), + /// There can only be a single global transition between any state pair + Global(StateId, StateId), +} + +impl Default for TransitionId { + fn default() -> Self { + Self::Direct("".to_string()) + } +} + /// Specification of a state node in the low-level FSM #[derive(Reflect, Debug, Clone, Default)] pub struct State { pub id: StateId, pub graph: Handle, + pub global_transition: Option, +} + +#[derive(Reflect, Debug, Clone, Default)] +#[reflect(Default)] +pub struct GlobalTransition { + pub duration: f32, + pub graph: Handle, } /// Stores the positions of nodes in the canvas for the editor @@ -229,22 +259,68 @@ impl StateMachine { pub fn update_low_level_fsm(&mut self) { let mut llfsm = LowLevelStateMachine::new(); - llfsm.start_state = Some(self.start_state.clone()); + llfsm.start_state = Some(LowLevelStateId::HlState(self.start_state.clone())); llfsm.input_data = self.input_data.clone(); for state in self.states.values() { - llfsm.add_state(super::core::LowLevelState { - id: state.id.clone(), + llfsm.add_state(super::low_level::LowLevelState { + id: LowLevelStateId::HlState(state.id.clone()), graph: state.graph.clone(), - transition: None, + hl_transition: None, }); + + if state.global_transition.is_some() { + // TODO: the source state is inaccurate since it will come from several places + for source_state in self.states.values() { + if source_state.id != state.id { + let transition_id = + TransitionId::Global(source_state.id.clone(), state.id.clone()); + + llfsm.add_state(LowLevelState { + id: LowLevelStateId::GlobalTransition( + source_state.id.clone(), + state.id.clone(), + ), + graph: state.global_transition.as_ref().unwrap().graph.clone(), + hl_transition: Some(super::low_level::TransitionData { + source: source_state.id.clone(), + target: state.id.clone(), + hl_transition_id: transition_id.clone(), + duration: state.global_transition.as_ref().unwrap().duration, + }), + }); + llfsm.add_transition(LowLevelTransition { + id: LowLevelTransitionId::Start(transition_id.clone()), + source: LowLevelStateId::HlState(source_state.id.clone()), + target: LowLevelStateId::GlobalTransition( + source_state.id.clone(), + state.id.clone(), + ), + transition_type: LowLevelTransitionType::Global, + hl_source: source_state.id.clone(), + hl_target: state.id.clone(), + }); + llfsm.add_transition(LowLevelTransition { + id: LowLevelTransitionId::End(transition_id.clone()), + source: LowLevelStateId::GlobalTransition( + source_state.id.clone(), + state.id.clone(), + ), + target: LowLevelStateId::HlState(state.id.clone()), + transition_type: LowLevelTransitionType::Global, + hl_source: source_state.id.clone(), + hl_target: state.id.clone(), + }); + } + } + } } for transition in self.transitions.values() { - llfsm.add_state(super::core::LowLevelState { - id: format!("{}_state", transition.id), + llfsm.add_state(LowLevelState { + id: LowLevelStateId::DirectTransition(transition.id.clone()), graph: transition.graph.clone(), - transition: Some(super::core::TransitionData { + hl_transition: Some(super::low_level::TransitionData { source: transition.source.clone(), target: transition.target.clone(), hl_transition_id: transition.id.clone(), @@ -252,17 +328,23 @@ impl StateMachine { }), }); - llfsm.add_transition( - transition.source.clone(), - transition.id.clone(), - format!("{}_state", transition.id), - ); - - llfsm.add_transition( - format!("{}_state", transition.id), - "end_transition".into(), - transition.target.clone(), - ); + llfsm.add_transition(LowLevelTransition { + id: LowLevelTransitionId::Start(transition.id.clone()), + source: LowLevelStateId::HlState(transition.source.clone()), + target: LowLevelStateId::DirectTransition(transition.id.clone()), + transition_type: LowLevelTransitionType::Direct, + hl_source: transition.source.clone(), + hl_target: transition.target.clone(), + }); + + llfsm.add_transition(LowLevelTransition { + id: LowLevelTransitionId::End(transition.id.clone()), + source: LowLevelStateId::DirectTransition(transition.id.clone()), + target: LowLevelStateId::HlState(transition.target.clone()), + transition_type: LowLevelTransitionType::Direct, + hl_source: transition.source.clone(), + hl_target: transition.target.clone(), + }); } self.low_level_fsm = llfsm; diff --git a/crates/bevy_animation_graph/src/core/state_machine/high_level/serial.rs b/crates/bevy_animation_graph/src/core/state_machine/high_level/serial.rs index d4ebe3a..0448e04 100644 --- a/crates/bevy_animation_graph/src/core/state_machine/high_level/serial.rs +++ b/crates/bevy_animation_graph/src/core/state_machine/high_level/serial.rs @@ -2,15 +2,22 @@ use serde::{Deserialize, Serialize}; use crate::core::{animation_graph::PinMap, edge_data::DataValue}; -use super::{Extra, State, StateMachine, Transition}; +use super::{Extra, GlobalTransition, State, StateId, StateMachine, Transition, TransitionId}; -pub type StateIdSerial = String; -pub type TransitionIdSerial = String; +pub type StateIdSerial = StateId; +pub type TransitionIdSerial = TransitionId; #[derive(Serialize, Deserialize, Clone)] pub struct StateSerial { pub id: StateIdSerial, pub graph: String, + pub global_transition: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct GlobalTransitionSerial { + pub duration: f32, + pub graph: String, } #[derive(Serialize, Deserialize, Clone)] @@ -50,6 +57,19 @@ impl From<&State> for StateSerial { Self { id: value.id.clone(), graph: value.graph.path().unwrap().to_string(), + global_transition: value + .global_transition + .as_ref() + .map(GlobalTransitionSerial::from), + } + } +} + +impl From<&GlobalTransition> for GlobalTransitionSerial { + fn from(value: &GlobalTransition) -> Self { + Self { + duration: value.duration, + graph: value.graph.path().unwrap().to_string(), } } } diff --git a/crates/bevy_animation_graph/src/core/state_machine/core.rs b/crates/bevy_animation_graph/src/core/state_machine/low_level/mod.rs similarity index 66% rename from crates/bevy_animation_graph/src/core/state_machine/core.rs rename to crates/bevy_animation_graph/src/core/state_machine/low_level/mod.rs index 136ef05..f086cc9 100644 --- a/crates/bevy_animation_graph/src/core/state_machine/core.rs +++ b/crates/bevy_animation_graph/src/core/state_machine/low_level/mod.rs @@ -1,4 +1,5 @@ -use super::{StateId, TransitionId}; +use std::cmp::Ordering; + use crate::{ core::{ animation_graph::{ @@ -9,7 +10,7 @@ use crate::{ CacheReadFilter, CacheWriteFilter, FsmContext, PassContext, StateRole, StateStack, }, duration_data::DurationData, - edge_data::{DataValue, EventQueue}, + edge_data::{AnimationEvent, DataValue, EventQueue}, errors::GraphError, }, utils::unwrap::UnwrapVal, @@ -20,35 +21,100 @@ use bevy::{ utils::HashMap, }; +use super::high_level; + +#[derive(Reflect, Debug, Clone, PartialEq, Eq, Hash)] +pub enum LowLevelTransitionId { + Start(high_level::TransitionId), + End(high_level::TransitionId), +} + +#[derive(Reflect, Debug, Clone, PartialEq, Eq, Hash)] +pub enum LowLevelStateId { + HlState(high_level::StateId), + DirectTransition(high_level::TransitionId), + GlobalTransition( + /// source + high_level::StateId, + /// target (state with global transition enabled) + high_level::StateId, + ), +} + +#[derive(Reflect, Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum LowLevelTransitionType { + Direct, + Global, + Fallback, +} + +impl PartialOrd for LowLevelTransitionType { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for LowLevelTransitionType { + fn cmp(&self, other: &Self) -> Ordering { + use LowLevelTransitionType::*; + match (self, other) { + (Direct, Direct) => Ordering::Equal, + (Direct, Global) => Ordering::Less, + (Direct, Fallback) => Ordering::Less, + (Global, Direct) => Ordering::Greater, + (Global, Global) => Ordering::Equal, + (Global, Fallback) => Ordering::Less, + (Fallback, Direct) => Ordering::Greater, + (Fallback, Global) => Ordering::Greater, + (Fallback, Fallback) => Ordering::Equal, + } + } +} + /// Stateful data associated with an FSM node -#[derive(Reflect, Debug, Default, Clone)] +#[derive(Reflect, Debug, Clone)] pub struct FSMState { - pub state: StateId, + pub state: LowLevelStateId, pub state_entered_time: f32, } #[derive(Reflect, Debug, Clone)] pub struct TransitionData { - pub source: StateId, - pub target: StateId, - pub hl_transition_id: TransitionId, + pub source: high_level::StateId, + pub target: high_level::StateId, + pub hl_transition_id: high_level::TransitionId, pub duration: f32, } /// Specification of a state node in the low-level FSM #[derive(Reflect, Debug, Clone)] pub struct LowLevelState { - pub id: StateId, + pub id: LowLevelStateId, pub graph: Handle, - pub transition: Option, + pub hl_transition: Option, +} + +/// Specification of a transition in the low-level FSM +#[derive(Reflect, Debug, Clone)] +pub struct LowLevelTransition { + pub id: LowLevelTransitionId, + pub source: LowLevelStateId, + pub target: LowLevelStateId, + pub transition_type: LowLevelTransitionType, + pub hl_source: high_level::StateId, + pub hl_target: high_level::StateId, } /// It's a state machine in the mathematical sense (-ish). Transitions are immediate. #[derive(Asset, Reflect, Debug, Clone, Default)] pub struct LowLevelStateMachine { - pub states: HashMap, - pub transitions: HashMap<(StateId, TransitionId), StateId>, - pub start_state: Option, + pub states: HashMap, + + pub transitions: HashMap, + pub transitions_by_hl_state_pair: + HashMap<(high_level::StateId, high_level::StateId), Vec>, + + pub start_state: Option, pub input_data: PinMap, } @@ -69,6 +135,7 @@ impl LowLevelStateMachine { Self { states: HashMap::new(), transitions: HashMap::new(), + transitions_by_hl_state_pair: HashMap::new(), start_state: None, input_data: PinMap::new(), } @@ -78,8 +145,18 @@ impl LowLevelStateMachine { self.states.insert(state.id.clone(), state); } - pub fn add_transition(&mut self, from: StateId, transition: TransitionId, to: StateId) { - self.transitions.insert((from, transition), to); + pub fn add_transition(&mut self, transition: LowLevelTransition) { + self.transitions + .insert(transition.id.clone(), transition.clone()); + if matches!(transition.id, LowLevelTransitionId::Start(_)) { + let vec = self + .transitions_by_hl_state_pair + .entry((transition.hl_source.clone(), transition.hl_target.clone())) + .or_default(); + vec.push(transition.id.clone()); + // Direct transitions should come first + vec.sort_by_key(|id| self.transitions.get(id).unwrap().transition_type); + } } fn handle_event_queue( @@ -105,12 +182,52 @@ impl LowLevelStateMachine { }); for event in event_queue.events { - let transition = event.event.id; - if let Some(state) = self.transitions.get(&(fsm_state.state.clone(), transition)) { - fsm_state = FSMState { - state: state.clone(), - state_entered_time: time, - }; + match event.event { + AnimationEvent::TransitionToState(hl_target_state_id) => { + if let LowLevelStateId::HlState(hl_curr_state_id) = fsm_state.state.clone() { + if let Some(transition) = self + .transitions_by_hl_state_pair + .get(&(hl_curr_state_id, hl_target_state_id)) + .and_then(|ids| ids.iter().next()) + .and_then(|id| self.transitions.get(id)) + { + fsm_state = FSMState { + state: transition.target.clone(), + state_entered_time: time, + }; + } + } + } + AnimationEvent::Transition(transition_id) => { + if let Some(transition) = self + .transitions + .get(&LowLevelTransitionId::Start(transition_id)) + { + if fsm_state.state == transition.source { + fsm_state = FSMState { + state: transition.target.clone(), + state_entered_time: time, + }; + } + } + } + AnimationEvent::EndTransition => { + if let Some(hl_transition_data) = self + .states + .get(&fsm_state.state) + .and_then(|s| s.hl_transition.as_ref()) + { + if let Some(transition) = self.transitions.get(&LowLevelTransitionId::End( + hl_transition_data.hl_transition_id.clone(), + )) { + fsm_state = FSMState { + state: transition.target.clone(), + state_entered_time: time, + }; + } + } + } + AnimationEvent::StringId(_) => {} } } @@ -159,7 +276,7 @@ impl LowLevelStateMachine { let time = ctx.time(); let elapsed_time = time - fsm_state.state_entered_time; let percent_through_duration = state - .transition + .hl_transition .as_ref() .map(|t| elapsed_time / t.duration) .unwrap_or(0.); @@ -227,22 +344,26 @@ impl LowLevelStateMachine { } else { let (queried_state, queried_role) = if s == Self::SOURCE_POSE { ( - state - .transition - .as_ref() - .ok_or(GraphError::FSMExpectedTransitionFoundState)? - .source - .clone(), + LowLevelStateId::HlState( + state + .hl_transition + .as_ref() + .ok_or(GraphError::FSMExpectedTransitionFoundState)? + .source + .clone(), + ), StateRole::Source, ) } else if s == Self::TARGET_POSE { ( - state - .transition - .as_ref() - .ok_or(GraphError::FSMExpectedTransitionFoundState)? - .target - .clone(), + LowLevelStateId::HlState( + state + .hl_transition + .as_ref() + .ok_or(GraphError::FSMExpectedTransitionFoundState)? + .target + .clone(), + ), StateRole::Target, ) } else { @@ -352,22 +473,26 @@ impl LowLevelStateMachine { SourcePin::InputTime(p) => { let (queried_state, queried_role) = if p == Self::SOURCE_TIME { ( - state - .transition - .as_ref() - .ok_or(GraphError::FSMExpectedTransitionFoundState)? - .source - .clone(), + LowLevelStateId::HlState( + state + .hl_transition + .as_ref() + .ok_or(GraphError::FSMExpectedTransitionFoundState)? + .source + .clone(), + ), StateRole::Source, ) } else if p == Self::TARGET_TIME { ( - state - .transition - .as_ref() - .ok_or(GraphError::FSMExpectedTransitionFoundState)? - .target - .clone(), + LowLevelStateId::HlState( + state + .hl_transition + .as_ref() + .ok_or(GraphError::FSMExpectedTransitionFoundState)? + .target + .clone(), + ), StateRole::Source, ) } else { diff --git a/crates/bevy_animation_graph/src/core/state_machine/mod.rs b/crates/bevy_animation_graph/src/core/state_machine/mod.rs index 33c9bff..a70e0a8 100644 --- a/crates/bevy_animation_graph/src/core/state_machine/mod.rs +++ b/crates/bevy_animation_graph/src/core/state_machine/mod.rs @@ -1,7 +1,2 @@ -mod core; - -mod common; pub mod high_level; - -pub use common::*; -pub use core::*; +pub mod low_level; diff --git a/crates/bevy_animation_graph/src/nodes/fsm_node.rs b/crates/bevy_animation_graph/src/nodes/fsm_node.rs index 2eced39..356eadf 100644 --- a/crates/bevy_animation_graph/src/nodes/fsm_node.rs +++ b/crates/bevy_animation_graph/src/nodes/fsm_node.rs @@ -4,7 +4,7 @@ use crate::core::{ context::{PassContext, SpecContext}, edge_data::DataSpec, errors::GraphError, - state_machine::{high_level::StateMachine, LowLevelStateMachine}, + state_machine::{high_level::StateMachine, low_level::LowLevelStateMachine}, }; use bevy::prelude::*; diff --git a/crates/bevy_animation_graph_editor/src/egui_fsm/lib.rs b/crates/bevy_animation_graph_editor/src/egui_fsm/lib.rs index 34f9e6d..f9b7e4b 100644 --- a/crates/bevy_animation_graph_editor/src/egui_fsm/lib.rs +++ b/crates/bevy_animation_graph_editor/src/egui_fsm/lib.rs @@ -400,7 +400,11 @@ impl FsmUiContext { |ui| { let mut title_info = None; let titlebar_shape = ui.painter().add(egui::Shape::Noop); - let node_title = node.spec.name.clone(); + let node_title = if node.spec.has_global_transition { + format!("⚡ {}", node.spec.name.clone()) + } else { + node.spec.name.clone() + }; let response = ui.allocate_ui(ui.available_size(), |ui| ui.label(node_title)); let title_bar_content_rect = response.response.rect; title_info.replace((titlebar_shape, title_bar_content_rect)); diff --git a/crates/bevy_animation_graph_editor/src/egui_fsm/node.rs b/crates/bevy_animation_graph_editor/src/egui_fsm/node.rs index 752c54e..bb4e9e4 100644 --- a/crates/bevy_animation_graph_editor/src/egui_fsm/node.rs +++ b/crates/bevy_animation_graph_editor/src/egui_fsm/node.rs @@ -53,6 +53,7 @@ pub struct StateSpec { pub(crate) duration: Option, pub(crate) active: bool, pub(crate) is_start_state: bool, + pub(crate) has_global_transition: bool, } #[derive(Derivative, Clone)] diff --git a/crates/bevy_animation_graph_editor/src/fsm_show.rs b/crates/bevy_animation_graph_editor/src/fsm_show.rs index 1987220..918f5c5 100644 --- a/crates/bevy_animation_graph_editor/src/fsm_show.rs +++ b/crates/bevy_animation_graph_editor/src/fsm_show.rs @@ -4,8 +4,8 @@ use crate::egui_fsm::{ }; use bevy::{asset::Assets, math::Vec2, utils::HashMap}; use bevy_animation_graph::core::state_machine::{ - high_level::{State, StateMachine, Transition}, - FSMState, StateId, TransitionId, + high_level::{State, StateId, StateMachine, Transition, TransitionId}, + low_level::{FSMState, LowLevelStateId}, }; use bevy_inspector_egui::egui::Color32; @@ -159,7 +159,7 @@ impl FsmReprSpec { return false; }; - fsm_state.state == node.id + fsm_state.state == LowLevelStateId::HlState(node.id.clone()) } fn transition_debug_info( @@ -174,7 +174,7 @@ impl FsmReprSpec { fsm.get_low_level_fsm() .states .get(&fsm_state.state) - .and_then(|s| s.transition.as_ref()) + .and_then(|s| s.hl_transition.as_ref()) .map(|t| t.hl_transition_id == transition.id) .unwrap_or(false) } @@ -205,6 +205,7 @@ impl FsmReprSpec { duration: None, active, is_start_state: state.id == fsm.start_state, + has_global_transition: state.global_transition.is_some(), ..Default::default() }; diff --git a/crates/bevy_animation_graph_editor/src/graph_update.rs b/crates/bevy_animation_graph_editor/src/graph_update.rs index c1ba74b..631efaa 100644 --- a/crates/bevy_animation_graph_editor/src/graph_update.rs +++ b/crates/bevy_animation_graph_editor/src/graph_update.rs @@ -15,10 +15,7 @@ use bevy_animation_graph::core::{ animation_node::AnimationNode, context::SpecContext, edge_data::DataValue, - state_machine::{ - high_level::{State, StateMachine, Transition}, - StateId, TransitionId, - }, + state_machine::high_level::{State, StateId, StateMachine, Transition, TransitionId}, }; use super::egui_fsm::lib::EguiFsmChange; @@ -309,7 +306,7 @@ pub fn convert_fsm_change( .unwrap(); GlobalChange::FsmChange { asset_id: graph_id, - change: FsmChange::TransitionDeleted(transition_name.to_string()), + change: FsmChange::TransitionDeleted(transition_name.clone()), } } EguiFsmChange::StateRemoved(state_id) => { diff --git a/crates/bevy_animation_graph_editor/src/ui.rs b/crates/bevy_animation_graph_editor/src/ui.rs index f74f363..5402005 100644 --- a/crates/bevy_animation_graph_editor/src/ui.rs +++ b/crates/bevy_animation_graph_editor/src/ui.rs @@ -30,8 +30,9 @@ use bevy_animation_graph::core::animation_graph_player::AnimationGraphPlayer; use bevy_animation_graph::core::animation_node::AnimationNode; use bevy_animation_graph::core::context::{GraphContext, GraphContextId, SpecContext}; use bevy_animation_graph::core::edge_data::AnimationEvent; -use bevy_animation_graph::core::state_machine::high_level::{State, StateMachine, Transition}; -use bevy_animation_graph::core::state_machine::{StateId, TransitionId}; +use bevy_animation_graph::core::state_machine::high_level::{ + State, StateId, StateMachine, Transition, TransitionId, +}; use bevy_egui::EguiContext; use bevy_inspector_egui::bevy_egui::EguiUserTextures; use bevy_inspector_egui::reflect_inspector::{Context, InspectorUi}; @@ -102,9 +103,9 @@ pub struct SceneSelection { scene: Handle, respawn: bool, active_context: HashMap, - event_table: Vec, + event_table: Vec, /// Just here as a buffer for the editor - temp_event_val: String, + event_editor: AnimationEvent, } #[derive(Default)] @@ -567,10 +568,29 @@ impl TabViewer<'_> { } fn event_sender(world: &mut World, ui: &mut egui::Ui, selection: &mut EditorSelection) { + let unsafe_world = world.as_unsafe_world_cell(); + let type_registry = unsafe { + unsafe_world + .get_resource::() + .unwrap() + .0 + .clone() + }; + + let type_registry = type_registry.read(); + let mut queue = CommandQueue::default(); + let mut cx = Context { + world: Some(unsafe { unsafe_world.world_mut() }.into()), + queue: Some(&mut queue), + }; + let mut env = InspectorUi::for_bevy(&type_registry, &mut cx); + let Some(scene_selection) = &mut selection.scene else { return; }; - let Some(graph_player) = get_animation_graph_player_mut(world) else { + let Some(graph_player) = + get_animation_graph_player_mut(unsafe { unsafe_world.world_mut() }) + else { return; }; @@ -580,8 +600,8 @@ impl TabViewer<'_> { .stroke(egui::Stroke::new(1., egui::Color32::WHITE)) .show(ui, |ui| { ui.horizontal(|ui| { - if ui.button(ev).clicked() { - graph_player.send_event(AnimationEvent { id: ev.into() }); + if ui.button(format!("{:?}", ev)).clicked() { + graph_player.send_event(ev.clone()); } !ui.button("×").clicked() }) @@ -591,14 +611,14 @@ impl TabViewer<'_> { }); }); - ui.horizontal(|ui| { - ui.text_edit_singleline(&mut scene_selection.temp_event_val); - if ui.button("Add").clicked() { - scene_selection - .event_table - .push(scene_selection.temp_event_val.clone()); - } - }); + ui.separator(); + + env.ui_for_reflect(&mut scene_selection.event_editor, ui); + if ui.button("Add").clicked() { + scene_selection + .event_table + .push(scene_selection.event_editor.clone()); + } } fn fsm_editor( @@ -813,7 +833,7 @@ impl TabViewer<'_> { respawn: true, active_context: HashMap::default(), event_table, - temp_event_val: "".into(), + event_editor: AnimationEvent::default(), }); } } diff --git a/examples/human_fsm/examples/human_fsm.rs b/examples/human_fsm/examples/human_fsm.rs index 5c85e86..594dc9a 100644 --- a/examples/human_fsm/examples/human_fsm.rs +++ b/examples/human_fsm/examples/human_fsm.rs @@ -122,14 +122,10 @@ fn keyboard_animation_control( } if keyboard_input.pressed(KeyCode::ArrowUp) { - player.send_event(AnimationEvent { - id: "speed_up".into(), - }) + player.send_event(AnimationEvent::TransitionToState("run".into())); } if keyboard_input.pressed(KeyCode::ArrowDown) { - player.send_event(AnimationEvent { - id: "slow_down".into(), - }) + player.send_event(AnimationEvent::TransitionToState("walk".into())); } if params.direction == Vec3::ZERO {