mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-18 07:35:19 +00:00
Auto keyframe/scrubbing/snapping (#107)
Co-authored-by: Danny McGee <dannymcgee@gmail.com>
This commit is contained in:
@@ -33,7 +33,8 @@ impl Plugin for TimelinePlugin {
|
||||
.register_type::<EaseType>()
|
||||
.register_type::<uuid::Uuid>()
|
||||
.add_event::<TimelineInteractionEvent>()
|
||||
.add_event::<KeyframeInteractionEvent>();
|
||||
.add_event::<KeyframeInteractionEvent>()
|
||||
.add_state::<KeyingMode>();
|
||||
|
||||
app.insert_resource(GlobalSeekTime {
|
||||
time: 0.,
|
||||
@@ -48,6 +49,7 @@ impl Plugin for TimelinePlugin {
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
process_auto_key.run_if(in_state(KeyingMode::Auto)),
|
||||
process_keyframe_events,
|
||||
timeline_bound_component_animator_system::<Transform>,
|
||||
),
|
||||
@@ -70,6 +72,7 @@ impl Plugin for TimelinePlugin {
|
||||
wasm::notify_seek_time_changes.map(bevy::utils::error),
|
||||
wasm::notify_keyframe_changes.map(bevy::utils::error),
|
||||
wasm::notify_updated_animation_targets.map(bevy::utils::error),
|
||||
wasm::notify_keying_mode_changed.map(bevy::utils::error),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -84,6 +87,7 @@ pub enum TimelineInteractionEvent {
|
||||
Play,
|
||||
Pause,
|
||||
Seek(f32),
|
||||
ToggleAutoKey,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@@ -102,6 +106,13 @@ pub struct GlobalSeekTime {
|
||||
pub delta: Option<f32>,
|
||||
}
|
||||
|
||||
#[derive(States, Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub enum KeyingMode {
|
||||
#[default]
|
||||
Manual,
|
||||
Auto,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, Default, Reflect, PartialEq, Serialize, Deserialize)]
|
||||
pub enum EaseType {
|
||||
#[default]
|
||||
@@ -246,6 +257,8 @@ impl FromStr for AnimationTarget {
|
||||
fn process_timeline_events(
|
||||
mut e_timeline_event: EventReader<TimelineInteractionEvent>,
|
||||
mut r_global_seek_time: ResMut<GlobalSeekTime>,
|
||||
key_mode: Res<State<KeyingMode>>,
|
||||
mut next_keying_mode: ResMut<NextState<KeyingMode>>,
|
||||
) {
|
||||
for event in e_timeline_event.read() {
|
||||
match event {
|
||||
@@ -256,6 +269,12 @@ fn process_timeline_events(
|
||||
TimelineInteractionEvent::Seek(time) => {
|
||||
r_global_seek_time.time = time.max(0.);
|
||||
}
|
||||
TimelineInteractionEvent::ToggleAutoKey => {
|
||||
next_keying_mode.set(match key_mode.get() {
|
||||
KeyingMode::Manual => KeyingMode::Auto,
|
||||
KeyingMode::Auto => KeyingMode::Manual,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
r_global_seek_time.delta = None;
|
||||
@@ -344,6 +363,22 @@ fn process_keyframe_events(
|
||||
}
|
||||
}
|
||||
|
||||
fn process_auto_key(
|
||||
changed: Query<Entity, (With<AnimationTarget>, Changed<Transform>)>,
|
||||
r_global_seek_time: Res<GlobalSeekTime>,
|
||||
mut event: EventWriter<KeyframeInteractionEvent>,
|
||||
) {
|
||||
if r_global_seek_time.playing || r_global_seek_time.is_changed() {
|
||||
info!("Skipping Auto Key");
|
||||
return;
|
||||
}
|
||||
|
||||
for entity in &changed {
|
||||
info!("Setting Auto Key");
|
||||
event.send(KeyframeInteractionEvent::RecordKeyframe { target: entity })
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_transform_animation(
|
||||
mut commands: Commands,
|
||||
keyframes: Query<
|
||||
@@ -578,7 +613,7 @@ mod wasm {
|
||||
};
|
||||
|
||||
use super::{
|
||||
AnimationTarget, EaseType, GlobalSeekTime, Keyframe, KeyframeInteractionEvent,
|
||||
AnimationTarget, EaseType, GlobalSeekTime, Keyframe, KeyframeInteractionEvent, KeyingMode,
|
||||
TimedAnimationClipAsset, TimelineInteractionEvent, TransformKeyframe,
|
||||
};
|
||||
|
||||
@@ -601,6 +636,11 @@ mod wasm {
|
||||
SEEK_TIME.write_message(TimelineInteractionEvent::Pause);
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = toggleAutoKey)]
|
||||
pub fn toggle_auto_key() {
|
||||
SEEK_TIME.write_message(TimelineInteractionEvent::ToggleAutoKey);
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = recordKeyframe)]
|
||||
pub fn record_keyframe(entities: Vec<String>) {
|
||||
KEYFRAME_EVENT.write_message(
|
||||
@@ -699,6 +739,13 @@ mod wasm {
|
||||
removed: string[]
|
||||
}
|
||||
}
|
||||
|
||||
export interface UpdateKeyingMode extends CustomEvent {
|
||||
type: "keying-mode-changed",
|
||||
detail: {
|
||||
automatic: boolean
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
export interface GlobalEventHandlersEventMap {
|
||||
@@ -722,6 +769,11 @@ mod wasm {
|
||||
removed: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct UpdateKeyingMode {
|
||||
automatic: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct TimedAnimationClipInfo {
|
||||
entity: String,
|
||||
@@ -914,4 +966,28 @@ mod wasm {
|
||||
&serde_json::to_value(r_global_seek_time.as_ref()).map_err(|e| format!("{e}"))?,
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn notify_keying_mode_changed(
|
||||
r_keying_mode: Res<State<KeyingMode>>,
|
||||
q_window: Query<&Window, With<PrimaryWindow>>,
|
||||
) -> Result<(), String> {
|
||||
if !r_keying_mode.is_changed() {
|
||||
return Ok(());
|
||||
}
|
||||
let Ok(window) = q_window.get_single() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
crate::wasm::dispatch_to_js(
|
||||
window,
|
||||
"keying-mode-changed",
|
||||
&serde_json::to_value(UpdateKeyingMode {
|
||||
automatic: match r_keying_mode.get() {
|
||||
KeyingMode::Manual => false,
|
||||
KeyingMode::Auto => true,
|
||||
},
|
||||
})
|
||||
.map_err(|e| format!("{e}"))?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user