Fix animation bug

The solution was to put all animations into one graph!

That way they can play together well.
main
Elijah Voigt 1 year ago
parent 44008a5b89
commit 4f5117f424

@ -0,0 +1,178 @@
use std::{f32::consts::PI, ops::RangeInclusive};
use bevy::{
animation::{animated_field, AnimationTarget, AnimationTargetId},
prelude::*,
utils::HashMap,
};
use crate::{
deck::Deck,
play::{self, Selected},
};
pub struct AnimationPlugin;
impl Plugin for AnimationPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, delayed_animation);
}
}
#[derive(Event, Clone)]
struct AnimationComplete;
#[derive(Resource, Default)]
pub(crate) struct AnimationStore {
pub store: HashMap<String, AnimationNodeIndex>,
pub graph: AnimationGraphHandle,
}
#[derive(Component)]
pub(crate) struct DelayedAnimation {
pub graph: AnimationGraphHandle,
pub animation_index: AnimationNodeIndex,
pub delay: Timer,
}
pub(crate) fn setup_animations(
mut clips: ResMut<Assets<AnimationClip>>,
mut graphs: ResMut<Assets<AnimationGraph>>,
mut commands: Commands,
) {
let mut animation_store = AnimationStore::default();
let mut animation_graph = AnimationGraph::new();
let targets: Vec<AnimationTargetId> = Deck::iter_cards()
.map(|c| Name::new(format!("{}", c)))
.map(|n| AnimationTargetId::from_name(&n))
.collect();
// Rotation Animation
{
let mut animation = AnimationClip::default();
targets.iter().for_each(|target| {
let curve = AnimatableCurve::new(
animated_field!(Transform::rotation),
AnimatableKeyframeCurve::new([0.0, 5.0, 10.0, 15.0, 20.0].into_iter().zip([
Quat::IDENTITY,
Quat::from_axis_angle(Vec3::Z, PI / 2.),
Quat::from_axis_angle(Vec3::Z, PI / 2. * 2.),
Quat::from_axis_angle(Vec3::Z, PI / 2. * 3.),
Quat::IDENTITY,
]))
.expect("Rotation animation"),
);
animation.add_curve_to_target(*target, curve);
});
let animation_handle = clips.add(animation);
let animation_index = animation_graph.add_clip(animation_handle, 1.0, animation_graph.root);
animation_store
.store
.insert("rotate".into(), animation_index);
}
{
// For each spot on board
RangeInclusive::<u8>::new(0, 3).for_each(|x| {
RangeInclusive::<u8>::new(0, 3).for_each(|y| {
let a = Vec3::new(-400.0, -200.0, 0.0);
let b = play::card_placement(&play::PlayLocation { x, y });
let c = Vec3::new(400.0, -200.0, 0.0);
// Serve Deck -> Spot Animation
{
let mut animation = AnimationClip::default();
targets.iter().for_each(|target| {
let curve = AnimatableCurve::new(
animated_field!(Transform::translation),
AnimatableKeyframeCurve::new([0.0, 1.0].into_iter().zip([a, b]))
.expect("Serve Card animation"),
);
animation.add_curve_to_target(*target, curve);
});
animation.set_duration(1.0);
animation.add_event(1.0, AnimationComplete);
let animation_handle = clips.add(animation);
let animation_index =
animation_graph.add_clip(animation_handle, 1.0, animation_graph.root);
animation_store
.store
.insert(format!("deck->{:?}", (x, y)), animation_index);
}
// Spot -> Discard Animation
{
let mut animation = AnimationClip::default();
targets.iter().for_each(|target| {
let curve = AnimatableCurve::new(
animated_field!(Transform::translation),
AnimatableKeyframeCurve::new([0.0, 1.0].into_iter().zip([b, c]))
.expect("Serve Card animation"),
);
animation.add_curve_to_target(*target, curve);
});
animation.set_duration(1.0);
animation.add_event(1.0, AnimationComplete);
let animation_handle = clips.add(animation);
let animation_index =
animation_graph.add_clip(animation_handle, 1.0, animation_graph.root);
animation_store
.store
.insert(format!("{:?}->discard", (x, y)), animation_index);
}
});
});
}
animation_store.graph = AnimationGraphHandle(graphs.add(animation_graph));
commands.insert_resource(animation_store);
// TODO: (example: https://bevyengine.org/examples/animation/animated-transform/)
// Button Animations:
// active_button_animation = AnimationClip::default() // color and size
}
pub(crate) fn play_selected_animation(
trigger: Trigger<OnInsert, Selected>,
mut query: Query<&mut AnimationPlayer>,
store: Res<AnimationStore>,
) {
let ai = store.store.get("rotate".into()).unwrap();
query
.get_mut(trigger.entity())
.unwrap()
.play(ai.clone())
.repeat();
}
pub(crate) fn stop_selected_animation(
trigger: Trigger<OnRemove, Selected>,
mut query: Query<(&mut Transform, &mut AnimationPlayer)>,
) {
let (mut t, mut ap) = query.get_mut(trigger.entity()).unwrap();
ap.stop_all();
t.rotation = Quat::default();
}
fn delayed_animation(
mut query: Query<(Entity, &mut DelayedAnimation, &mut AnimationPlayer), With<AnimationTarget>>,
mut commands: Commands,
time: Res<Time>,
) {
query
.iter_mut()
.for_each(|(entity, mut delayed_animation, mut animation_player)| {
delayed_animation.delay.tick(time.delta());
if delayed_animation.delay.just_finished() {
info!("Playing delayed animation for {:?}", entity);
animation_player.play(delayed_animation.animation_index);
commands
.entity(entity)
.insert(delayed_animation.graph.clone())
.remove::<DelayedAnimation>();
}
});
}

@ -1,6 +1,6 @@
use bevy::prelude::*;
use crate::deck::Card;
use crate::{animation::DelayedAnimation, deck::Card, play::PlayLocation, view::ViewState};
/// Debugging systems
pub struct DebugPlugin;
@ -9,6 +9,8 @@ impl Plugin for DebugPlugin {
fn build(&self, app: &mut App) {
app.add_observer(track_card_info)
.add_systems(Startup, init_ui)
.add_systems(OnEnter(ViewState::Play), infodump_play_cards)
.add_systems(Update, track_card_changes)
.add_systems(Update, set_debug_location);
}
}
@ -95,3 +97,20 @@ fn set_debug_location(
}
}
}
fn infodump_play_cards(query: Query<(&Transform, &PlayLocation)>) {
query.iter().for_each(|(t, pl)| {
info!("{:?} {:?}", t.translation, pl);
});
}
fn track_card_changes(
query: Query<
(Entity, &Transform, &PlayLocation),
(Changed<Transform>, With<Card>, With<DelayedAnimation>),
>,
) {
query.iter().for_each(|(e, t, pl)| {
info!("Changed {} {} {:?}", e, t.translation, pl);
});
}

@ -1,3 +1,4 @@
mod animation;
mod audio;
mod boot;
mod debug;
@ -25,6 +26,7 @@ fn main() {
}),
)
.add_plugins((
animation::AnimationPlugin,
audio::AudioPlugin,
boot::BootPlugin,
menu::MenuPlugin,
@ -46,5 +48,6 @@ pub(crate) enum GameState {
#[default]
Boot,
Setup,
NewGame,
Main,
}

@ -8,8 +8,9 @@ use bevy::{
use crate::{
audio, boot,
deck::{Card, Deck, ItemColor, ItemNumber, ItemPattern, ItemShape},
play::{check_for_sets, check_set, DeckOrder, DelayedAnimation, PlayLocation, SetNumber},
play::{check_for_sets, check_set, SetNumber},
view::{button_set_state, ViewState},
GameState,
};
/// Game Menu
@ -90,7 +91,6 @@ fn setup(mut commands: Commands, server: Res<AssetServer>) {
.observe(button_hover_on)
.observe(button_hover_off)
.observe(button_set_state(ViewState::Play));
/*
parent
.spawn(button_builder(Node::default()))
.with_children(|parent| {
@ -98,9 +98,7 @@ fn setup(mut commands: Commands, server: Res<AssetServer>) {
})
.observe(button_hover_on)
.observe(button_hover_off)
.observe(reset_game)
.observe(button_set_state(ViewState::Play));
*/
.observe(button_set_state(GameState::NewGame));
parent
.spawn(button_builder(Node::default()))
.with_children(|parent| {
@ -522,43 +520,6 @@ fn quit_button(_trigger: Trigger<Pointer<Click>>, mut exit_event: EventWriter<Ap
exit_event.send(AppExit::Success);
}
fn reset_game(
_trigger: Trigger<Pointer<Click>>,
cards: Query<Entity, With<Card>>,
deck: Res<Deck>,
mut commands: Commands,
) {
std::iter::zip(Deck::iter_shuffled().enumerate(), cards.iter()).for_each(
|((i, this_card), entity)| {
let this = deck
.cards
.get(&this_card)
.unwrap_or_else(|| panic!("fech card sprite {:?}", this_card))
.clone();
let this_sprite = Sprite {
custom_size: Some(Vec2::new(80.0, 128.0)),
..this
};
let this_transform =
Transform::default().with_translation(Vec3::new(-400.0, -200.0, 0.0));
let order = DeckOrder(i as u8);
commands
.entity(entity)
.remove::<SetNumber>()
.remove::<PlayLocation>()
.remove::<PickingBehavior>()
.remove::<DelayedAnimation>()
.insert((
this_sprite,
this_card,
this_transform,
order,
Visibility::Hidden,
));
},
);
}
fn button_hover_on(trigger: Trigger<Pointer<Over>>, mut query: Query<&mut BackgroundColor>) {
let mut background_color = query.get_mut(trigger.entity()).unwrap();
background_color.0.set_alpha(1.0);

@ -1,10 +1,12 @@
use bevy::{prelude::*, utils::RandomState};
use crate::{
deck::Card,
animation::{AnimationStore, DelayedAnimation},
deck::{Card, Deck},
menu::UiMessage,
setup::{AnimationStore, TopCard},
setup::TopCard,
view::ViewState,
GameState,
};
pub struct PlayPlugin;
@ -13,9 +15,9 @@ impl Plugin for PlayPlugin {
fn build(&self, app: &mut App) {
app.add_event::<ServeCards>()
.add_systems(Update, serve_cards)
.add_systems(OnEnter(GameState::NewGame), reset_game)
.add_systems(OnEnter(ViewState::Play), deal_cards)
.add_systems(Update, deal_cards.run_if(set_added))
.add_systems(Update, delayed_animation);
.add_systems(Update, deal_cards.run_if(set_added));
}
}
@ -53,30 +55,6 @@ pub(crate) fn toggle_selected(
}
}
pub(crate) fn play_selected_animation(
trigger: Trigger<OnInsert, Selected>,
mut query: Query<&mut AnimationPlayer>,
store: Res<AnimationStore>,
mut commands: Commands,
) {
let (g, ai) = store.store.get("rotate".into()).unwrap();
commands.entity(trigger.entity()).insert(g.clone());
query
.get_mut(trigger.entity())
.unwrap()
.play(ai.clone())
.repeat();
}
pub(crate) fn stop_selected_animation(
trigger: Trigger<OnRemove, Selected>,
mut query: Query<(&mut Transform, &mut AnimationPlayer)>,
) {
let (mut t, mut ap) = query.get_mut(trigger.entity()).unwrap();
ap.stop_all();
t.rotation = Quat::default();
}
/// Check a set when the "Set" button is clicked
pub(crate) fn check_set(
_trigger: Trigger<Pointer<Click>>,
@ -97,7 +75,7 @@ pub(crate) fn check_set(
match is_set((a, b, c)) {
Ok(()) => {
query.iter().for_each(|(entity, _, play_location, parent)| {
let (graph_handle, animation_index) = animation_store
let animation_index = animation_store
.store
.get(&format!(
"{:?}->discard",
@ -116,7 +94,7 @@ pub(crate) fn check_set(
.remove::<Selected>()
.insert(DelayedAnimation {
delay: Timer::from_seconds(0.1, TimerMode::Once),
graph: graph_handle.clone(),
graph: animation_store.graph.clone(),
animation_index: animation_index.clone(),
})
.insert(SetNumber(set_number))
@ -202,11 +180,13 @@ fn serve_cards(
mut top_card: Single<&mut Visibility, With<TopCard>>,
mut commands: Commands,
) {
events.read().for_each(|_| {
if !events.is_empty() {
events.clear();
info!(
"Serving cards from deck ({} cards left)",
in_deck.iter().len()
);
info!("Taken spots: {}", spots.iter().len());
if in_deck.is_empty() {
commands.trigger(UiMessage("Deck's empty!".into()));
**top_card = Visibility::Hidden;
@ -240,6 +220,7 @@ fn serve_cards(
// If that search was fruitful, pull the card from the deck and play it
if let Some((e, _, _)) = candidate {
info!("Inserting location ({},{}) for {}", this_x, this_y, e);
commands
.entity(e)
.remove::<DeckOrder>()
@ -251,11 +232,12 @@ fn serve_cards(
}
}
}
});
}
}
/// Trigger dealing cards when the game starts
fn deal_cards(mut events: EventWriter<ServeCards>) {
info!("Dealing cards");
events.send(ServeCards);
}
@ -263,15 +245,27 @@ fn set_added(query: Query<Entity, Added<SetNumber>>) -> bool {
!query.is_empty()
}
#[derive(Component)]
pub(crate) struct DelayedAnimation {
graph: AnimationGraphHandle,
animation_index: AnimationNodeIndex,
delay: Timer,
pub(crate) fn shuffle_card(
trigger: Trigger<OnInsert, DeckOrder>,
mut commands: Commands,
mut query: Query<(&mut Transform, &mut Visibility, &mut AnimationPlayer)>,
) {
{
let (mut t, mut _v, mut ap) = query.get_mut(trigger.entity()).unwrap();
t.translation = Vec3::new(-400.0, -200.0, 0.0);
// *v = Visibility::Hidden;
ap.rewind_all();
}
commands
.entity(trigger.entity())
.remove::<PlayLocation>()
.remove::<SetNumber>();
}
#[derive(Event, Clone)]
pub(crate) struct AnimationComplete;
//
// OnRemove, SetNumber
// .remove::<PickingBehavior>()
//
/// When a card is added to the board, place it on the screen
pub(crate) fn place_card(
@ -283,7 +277,7 @@ pub(crate) fn place_card(
) {
let (entity, play_location, mut visibility) = query.get_mut(trigger.entity()).unwrap();
let (graph_handle, animation_index) = animation_store
let animation_index = animation_store
.store
.get(&format!("deck->{:?}", (play_location.x, play_location.y)))
.unwrap();
@ -294,33 +288,13 @@ pub(crate) fn place_card(
commands.entity(entity).insert(DelayedAnimation {
animation_index: animation_index.clone(),
delay: Timer::from_seconds(delay, TimerMode::Once),
graph: graph_handle.clone(),
graph: animation_store.graph.clone(),
});
// Set it to visible
*visibility = Visibility::Inherited;
}
fn delayed_animation(
mut query: Query<(Entity, &mut DelayedAnimation, &mut AnimationPlayer)>,
mut commands: Commands,
time: Res<Time>,
) {
query
.iter_mut()
.for_each(|(entity, mut delayed_animation, mut animation_player)| {
delayed_animation.delay.tick(time.delta());
if delayed_animation.delay.just_finished() {
animation_player.rewind_all();
animation_player.play(delayed_animation.animation_index);
commands
.entity(entity)
.insert(delayed_animation.graph.clone())
.remove::<DelayedAnimation>();
}
});
}
pub(crate) fn card_placement(PlayLocation { x, y }: &PlayLocation) -> Vec3 {
let card_size = [100.0, 160.0];
let offset = Vec2::new(card_size[0] * 4.0, card_size[1] * 4.0) / 2.5;
@ -391,3 +365,16 @@ pub(crate) fn check_for_sets(
commands.trigger(UiMessage("I'm stumped!".into()));
}
}
fn reset_game(
mut view_state: ResMut<NextState<ViewState>>,
mut game_state: ResMut<NextState<GameState>>,
cards: Query<Entity, With<Card>>,
mut commands: Commands,
) {
std::iter::zip(Deck::iter_shuffled().enumerate(), cards.iter()).for_each(|((i, _), entity)| {
commands.entity(entity).insert(DeckOrder(i as u8));
});
view_state.set(ViewState::Play);
game_state.set(GameState::Main);
}

@ -1,12 +1,9 @@
use std::{f32::consts::PI, ops::RangeInclusive};
use crate::{deck::*, *};
use animation::AnimationStore;
use bevy::{
animation::{animated_field, AnimationTarget, AnimationTargetId},
animation::{AnimationTarget, AnimationTargetId},
prelude::*,
utils::HashMap,
};
use play::AnimationComplete;
use view::ViewState;
pub struct SetupPlugin;
@ -17,21 +14,16 @@ impl Plugin for SetupPlugin {
OnEnter(GameState::Setup),
(
setup_background,
animation::setup_animations,
setup_cards,
setup_camera,
start_play,
setup_animations,
)
.chain(),
);
}
}
#[derive(Resource, Default)]
pub(crate) struct AnimationStore {
pub store: HashMap<String, (AnimationGraphHandle, AnimationNodeIndex)>,
}
#[derive(Component)]
pub(crate) struct TopCard;
@ -41,6 +33,7 @@ pub(crate) fn setup_cards(
deck: Res<Deck>,
mut layouts: ResMut<Assets<TextureAtlasLayout>>,
server: Res<AssetServer>,
animation_store: Res<AnimationStore>,
) {
let animation_player = AnimationPlayer::default();
commands
@ -124,116 +117,20 @@ pub(crate) fn setup_cards(
this_sprite,
this_transform,
visibility,
animation_store.graph.clone(),
))
.observe(play::shuffle_card)
.observe(play::place_card)
.observe(debug::set_debug_card)
.observe(debug::hide_debug_card)
.observe(play::play_selected_animation)
.observe(play::stop_selected_animation)
.observe(animation::play_selected_animation)
.observe(animation::stop_selected_animation)
.observe(play::toggle_selected);
});
});
});
}
fn setup_animations(
mut clips: ResMut<Assets<AnimationClip>>,
mut graphs: ResMut<Assets<AnimationGraph>>,
mut commands: Commands,
) {
let mut animation_store = AnimationStore::default();
let targets: Vec<AnimationTargetId> = Deck::iter_cards()
.map(|c| Name::new(format!("{}", c)))
.map(|n| AnimationTargetId::from_name(&n))
.collect();
// Rotation Animation
{
let mut animation = AnimationClip::default();
targets.iter().for_each(|target| {
let curve = AnimatableCurve::new(
animated_field!(Transform::rotation),
AnimatableKeyframeCurve::new([0.0, 5.0, 10.0, 15.0, 20.0].into_iter().zip([
Quat::IDENTITY,
Quat::from_axis_angle(Vec3::Z, PI / 2.),
Quat::from_axis_angle(Vec3::Z, PI / 2. * 2.),
Quat::from_axis_angle(Vec3::Z, PI / 2. * 3.),
Quat::IDENTITY,
]))
.expect("Rotation animation"),
);
animation.add_curve_to_target(*target, curve);
});
let animation_handle = clips.add(animation);
let (graph, animation_index) = AnimationGraph::from_clip(animation_handle);
let graph_handle = AnimationGraphHandle(graphs.add(graph));
animation_store
.store
.insert("rotate".into(), (graph_handle, animation_index));
}
{
// For each spot on board
RangeInclusive::<u8>::new(0, 3).for_each(|x| {
RangeInclusive::<u8>::new(0, 3).for_each(|y| {
let a = Vec3::new(-400.0, -200.0, 0.0);
let b = play::card_placement(&play::PlayLocation { x, y });
let c = Vec3::new(400.0, -200.0, 0.0);
// Serve Deck -> Spot Animation
{
let mut animation = AnimationClip::default();
targets.iter().for_each(|target| {
let curve = AnimatableCurve::new(
animated_field!(Transform::translation),
AnimatableKeyframeCurve::new([0.0, 1.0].into_iter().zip([a, b]))
.expect("Serve Card animation"),
);
animation.add_curve_to_target(*target, curve);
});
animation.set_duration(1.0);
animation.add_event(1.0, AnimationComplete);
let animation_handle = clips.add(animation);
let (graph, animation_index) = AnimationGraph::from_clip(animation_handle);
let graph_handle = AnimationGraphHandle(graphs.add(graph));
animation_store.store.insert(
format!("deck->{:?}", (x, y)),
(graph_handle, animation_index),
);
}
// Spot -> Discard Animation
{
let mut animation = AnimationClip::default();
targets.iter().for_each(|target| {
let curve = AnimatableCurve::new(
animated_field!(Transform::translation),
AnimatableKeyframeCurve::new([0.0, 1.0].into_iter().zip([b, c]))
.expect("Serve Card animation"),
);
animation.add_curve_to_target(*target, curve);
});
animation.set_duration(1.0);
animation.add_event(1.0, AnimationComplete);
let animation_handle = clips.add(animation);
let (graph, animation_index) = AnimationGraph::from_clip(animation_handle);
let graph_handle = AnimationGraphHandle(graphs.add(graph));
animation_store.store.insert(
format!("{:?}->discard", (x, y)),
(graph_handle, animation_index),
);
}
});
});
}
commands.insert_resource(animation_store);
// TODO: (example: https://bevyengine.org/examples/animation/animated-transform/)
// Button Animations:
// active_button_animation = AnimationClip::default() // color and size
}
/// Setup our camera to view cardson the screen
pub(crate) fn setup_camera(mut commands: Commands) {
commands.spawn((

Loading…
Cancel
Save