Skip to content

Commit

Permalink
wip: scene-based animation
Browse files Browse the repository at this point in the history
  • Loading branch information
mrchantey committed Jul 4, 2024
1 parent 6aae75c commit f8a96c0
Show file tree
Hide file tree
Showing 10 changed files with 285 additions and 35 deletions.
20 changes: 10 additions & 10 deletions crates/beet_core/src/animation/insert_on_animation_end.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ use std::time::Duration;
#[derive(Debug, Clone, Component, Reflect)]
#[reflect(Component, ActionMeta)]
/// Inserts the given component when an animation is almost finished.
/// Requires a [`Handle<AnimationClip>`] component.
pub struct InsertOnAnimationEnd<T: GenericActionComponent> {
pub value: T,
pub animation_clip: Handle<AnimationClip>,
pub animation_index: AnimationNodeIndex,
/// The duration of the transition to the next action.
/// This should be greater than frame delta time or there will be no chance
Expand All @@ -30,14 +30,9 @@ impl<T: GenericActionComponent> ActionSystems for InsertOnAnimationEnd<T> {


impl<T: GenericActionComponent> InsertOnAnimationEnd<T> {
pub fn new(
clip: Handle<AnimationClip>,
index: AnimationNodeIndex,
value: T,
) -> Self {
pub fn new(index: AnimationNodeIndex, value: T) -> Self {
Self {
value,
animation_clip: clip,
animation_index: index,
transition_duration: DEFAULT_ANIMATION_TRANSITION,
}
Expand All @@ -54,11 +49,16 @@ pub fn insert_on_animation_end<T: GenericActionComponent>(
children: Query<&Children>,
clips: Res<Assets<AnimationClip>>,
mut query: Query<
(Entity, &TargetAgent, &InsertOnAnimationEnd<T>),
(
Entity,
&TargetAgent,
&InsertOnAnimationEnd<T>,
&Handle<AnimationClip>,
),
With<Running>,
>,
) {
for (entity, agent, action) in query.iter_mut() {
for (entity, agent, action, handle) in query.iter_mut() {
let Some(target) = ChildrenExt::first(**agent, &children, |entity| {
animators.contains(entity)
}) else {
Expand All @@ -67,7 +67,7 @@ pub fn insert_on_animation_end<T: GenericActionComponent>(
// safe unwrap, just checked
let player = animators.get(target).unwrap();

let Some(clip) = clips.get(&action.animation_clip) else {
let Some(clip) = clips.get(handle) else {
continue;
};

Expand Down
13 changes: 10 additions & 3 deletions crates/beet_examples/examples/build_scenes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@ fn main() -> Result<()> {
SceneItem::new_bundle("camera-2d", BundlePlaceholder::Camera2d),
SceneItem::new_bundle("camera-3d", BundlePlaceholder::Camera3d),
SceneItem::new_resource("beet-debug", BeetDebugConfig::default()),
SceneItem::new("space-scene", scenes::space_scene),
// text
SceneItem::new("ui-terminal", spawn_ui_terminal),
SceneItem::new("seek", scenes::seek),
SceneItem::new("flock", scenes::flock),
SceneItem::new("hello-world", scenes::hello_world),
SceneItem::new("hello-net", scenes::hello_net),
SceneItem::new("sentence-selector", scenes::sentence_selector),
// 2d
SceneItem::new("space-scene", scenes::space_scene),
SceneItem::new("seek", scenes::seek),
SceneItem::new("flock", scenes::flock),
// 3d
SceneItem::new("ground-3d", scenes::setup_ground_3d),
SceneItem::new("lighting-3d", scenes::setup_lighting_3d),
SceneItem::new("animation-demo", scenes::animation_demo),
],
}]
.into_iter()
Expand Down Expand Up @@ -92,6 +98,7 @@ impl SceneItem {
TransformPlugin::default(),
AssetPlugin::default(),
RenderPlugin::default(),
bevy::prelude::AnimationPlugin::default(),
UiPlugin::default(),
//beet
DefaultBeetPlugins::default(),
Expand Down
72 changes: 72 additions & 0 deletions crates/beet_examples/examples/scenes/animation_demo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use beet::prelude::*;
use beet_examples::prelude::*;
use bevy::animation::RepeatAnimation;
use bevy::prelude::*;
use std::time::Duration;


pub fn animation_demo(mut commands: Commands) {
// camera
commands.spawn((
BundlePlaceholder::Camera3d,
Transform::from_xyz(10.0, 10.0, 15.0)
.looking_at(Vec3::new(0.0, 2.0, 0.0), Vec3::Y),
));

let mut graph = AnimationGraphPlaceholder::default();

let anim1_clip =
AssetPlaceholder::<AnimationClip>::new("Fox.glb#Animation0");
let anim1_index = graph.add_clip(anim1_clip.clone(), 1.0, graph.root);
let anim2_clip =
AssetPlaceholder::<AnimationClip>::new("Fox.glb#Animation1");
let anim2_index = graph.add_clip(anim2_clip.clone(), 1.0, graph.root);

let transition_duration = Duration::from_secs_f32(0.5);

commands
.spawn((
Transform::from_scale(Vec3::splat(0.1)),
BundlePlaceholder::Scene("Fox.glb#Scene0".into()),
graph,
AnimationTransitions::new(),
))
.with_children(|parent| {
let agent = parent.parent_entity();
parent
.spawn((
Name::new("Animation Behavior"),
Running,
SequenceSelector,
Repeat,
))
.with_children(|parent| {
parent.spawn((
Name::new("Idle"),
TargetAgent(agent),
PlayAnimation::new(anim1_index)
.repeat(RepeatAnimation::Count(1))
.with_transition_duration(transition_duration),
anim1_clip,
InsertOnAnimationEnd::new(
anim1_index,
RunResult::Success,
)
.with_transition_duration(transition_duration),
));
parent.spawn((
Name::new("Walking"),
TargetAgent(agent),
PlayAnimation::new(anim2_index)
.repeat(RepeatAnimation::Count(4))
.with_transition_duration(transition_duration),
anim2_clip,
InsertOnAnimationEnd::new(
anim2_index,
RunResult::Success,
)
.with_transition_duration(transition_duration),
));
});
});
}
40 changes: 40 additions & 0 deletions crates/beet_examples/examples/scenes/setup_3d.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use beet_examples::prelude::*;
use bevy::pbr::CascadeShadowConfigBuilder;
use bevy::prelude::*;
use std::f32::consts::PI;


pub fn setup_ground_3d(mut commands: Commands) {
commands.spawn(BundlePlaceholder::Pbr {
mesh: MeshPlaceholder::Plane3d {
plane: Plane3d::default(),
width: 100.,
height: 100.,
},
material: MaterialPlaceholder::Color(Color::srgb(0.3, 0.5, 0.3)),
});
}


pub fn setup_lighting_3d(mut commands: Commands) {
// Light
commands.spawn(DirectionalLightBundle {
transform: Transform::from_rotation(Quat::from_euler(
EulerRot::ZYX,
0.0,
1.0,
-PI / 4.,
)),
directional_light: DirectionalLight {
shadows_enabled: true,
..default()
},
cascade_shadow_config: CascadeShadowConfigBuilder {
first_cascade_far_bound: 20.0,
maximum_distance: 40.0,
..default()
}
.into(),
..default()
});
}
43 changes: 24 additions & 19 deletions crates/beet_examples/src/plugins/example_plugin_group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,31 +47,36 @@ pub struct Example2dPlugin;
impl Plugin for Example2dPlugin {
fn build(&self, app: &mut App) {
app
.add_plugins(ReadyOnAssetLoadPlugin::<Image>::default())
.add_systems(Update, follow_cursor_2d)
// .add_systems(PreUpdate, auto_spawn::auto_spawn.before(PreTickSet))
.add_systems(Update, randomize_position.in_set(PreTickSet))
.add_systems(
Update,
(update_wrap_around, wrap_around)
.chain()
.run_if(|res: Option<Res<WrapAround>>| res.is_some())
.in_set(PostTickSet),
)
.register_type::<AutoSpawn>()
.register_type::<RandomizePosition>()
.register_type::<RenderText>()
.register_type::<WrapAround>()
.register_type::<FollowCursor2d>()
.register_type::<FollowCursor3d>()
/*_*/;
.add_plugins(ReadyOnAssetLoadPlugin::<Image>::default())
.add_systems(Update, follow_cursor_2d)
// .add_systems(PreUpdate, auto_spawn::auto_spawn.before(PreTickSet))
.add_systems(Update, randomize_position.in_set(PreTickSet))
.add_systems(
Update,
(update_wrap_around, wrap_around)
.chain()
.run_if(|res: Option<Res<WrapAround>>| res.is_some())
.in_set(PostTickSet),
)
.register_type::<AutoSpawn>()
.register_type::<RandomizePosition>()
.register_type::<RenderText>()
.register_type::<WrapAround>()
.register_type::<FollowCursor2d>()
.register_type::<FollowCursor3d>()
/*_*/;
}
}

pub struct Example3dPlugin;

impl Plugin for Example3dPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, (follow_cursor_3d, camera_distance));
app.add_plugins((
AnimationGraphPlaceholderPlugin,
AssetPlaceholderPlugin::<AnimationClip>::default(),
ReadyOnAssetLoadPlugin::<AnimationClip>::default(),
))
.add_systems(Update, (follow_cursor_3d, camera_distance));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use super::*;
use bevy::prelude::*;

pub struct AnimationGraphPlaceholderPlugin;
impl Plugin for AnimationGraphPlaceholderPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, init_animation_asset)
.register_type::<AnimationGraphPlaceholder>();
}
}


#[derive(Debug, Default, Component, Reflect)]
#[reflect(Default, Component)]
pub struct AnimationGraphPlaceholder {
pub root: AnimationNodeIndex,
pub clips: Vec<AnimationClipPlaceholder>,
}

impl AnimationGraphPlaceholder {
pub fn add_clip(
&mut self,
clip: AssetPlaceholder<AnimationClip>,
weight: f32,
parent: AnimationNodeIndex,
) -> AnimationNodeIndex {
self.clips.push(AnimationClipPlaceholder {
clip,
parent,
weight,
});
// thie first index is root
// we can safely determine the indices because we control
// the order in which they are added, see [`init_animation_asset`]
AnimationNodeIndex::new(self.clips.len())
}
}


#[derive(Debug, Reflect)]
pub struct AnimationClipPlaceholder {
pub clip: AssetPlaceholder<AnimationClip>,
pub parent: AnimationNodeIndex,
pub weight: f32,
}

/// This works in unison with the [`AssetPlaceholderPlugin`]
/// [`init_asset`]
/// [`init_asset`]: super::init_asset
pub fn init_animation_asset(
mut commands: Commands,
mut lookup: ResMut<AssetPlaceholderLookup<AnimationClip>>,
mut asset_server: ResMut<AssetServer>,
mut graphs: ResMut<Assets<AnimationGraph>>,
query: Query<
(Entity, &AnimationGraphPlaceholder),
Added<AnimationGraphPlaceholder>,
>,
) {
for (entity, placeholder) in query.iter() {
let mut graph = AnimationGraph::new();

for clip in &placeholder.clips {
let handle =
lookup.get_or_create(&mut asset_server, &clip.clip.path);
graph.add_clip(handle, clip.weight, clip.parent);
}
let handle = graphs.add(graph);

commands
.entity(entity)
.insert(handle)
.remove::<AnimationGraphPlaceholder>();
}
}
Loading

0 comments on commit f8a96c0

Please sign in to comment.