Help for the bevy_tween crate: https://github.com/Multirious/bevy_tween by @wooden zinc
#bevy_tween
529 messages · Page 1 of 1 (latest)
thanks!
trying to add this tween
self.commands.entity(entity).insert((
SpanTweenerBundle::new(Duration::from_millis(500)).tween_here(),
EaseFunction::BackIn,
ComponentTween::new(interpolate::Translation { start, end }),
));
to my player object that was created earlier
commands
.spawn((
Name::new("Player"),
SceneBundle {
scene: models.chara.clone(),
transform: player_transform,
..default()
},
...
nothing is happening when I insert the tween bundle in the first code block
I feel like im missing something basic, does this work out of the box with things other than sprites?
Hmm yeah lets see.
This should works because the tween won't care about anything else if it found the transform.
here is an easier to read view
and I can use the inspector to see the components getting inserted correctly
Hmm I'm seeing an Animator<Transform>. Is this running? It might be conflicting with the new tweens.
That is from previous use of bevy_tweening
let me make sure that doesn't get added
okay got rid of everything that could add that & am experiencing the same issue
is move_player running every frame or atleast run in multiples frame consecutively?
it is run on update and the system functions should be gated to only execuate once per key press
impl Plugin for PlayerMovementPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, move_action);
}
}
fn move_action(
query: Query<&ActionState<PlayerAction>, With<PlayerInput>>,
mut movement_system: MovementSystem,
) {
let action_state = query.get_single();
match action_state {
Ok(action_state) => {
if action_state.just_pressed(&PlayerAction::MoveUp) {
movement_system.move_player(PlayerAction::MoveUp);
} else if action_state.just_pressed(&PlayerAction::MoveDown) {
movement_system.move_player(PlayerAction::MoveDown);
} else if action_state.just_pressed(&PlayerAction::MoveLeft) {
movement_system.move_player(PlayerAction::MoveLeft);
} else if action_state.just_pressed(&PlayerAction::MoveRight) {
movement_system.move_player(PlayerAction::MoveRight);
}
}
Err(_) => todo!(),
}
}
made a minimal example, still having the problem with a basic 3d mesh
I'm sure that 3D does works because I just animate something with it yesterday. Maybe there's something else going on.
oh alright.
yeah you forgot DefaultTweenPlugins 🤣
HOLY SHIT
does it works?
trying now
love your detailed explanation btw :)
that was it !
gah, thank you for the sanity checking
okay excited to actually get into this plugin heh
thanks for using my crate!
I am excited to try the parallel tweening abilites!
@wooden zinc is there a way to pause all running tween animations and resume at a later time? I wonder if there is a scheduled time to operate pause/resume functions, something like
fn system(time: Res<Time<TweenSchedule>>) {
time.pause() // or unpause()
}
I'm experimenting with SkipTween in the following code (didn't work). I wonder if SkipTween is supposed to be put on entities with other components?
match self {
PlayState::Run => {
let animations: Vec<Entity> = world
.query_filtered::<Entity, With<SkipTween>>()
.iter_mut(world)
.collect();
for entity in animations {
world.entity_mut(entity).remove::<SkipTween>();
}
}
PlayState::Pause => {
let animations: Vec<Entity> = world
.query_filtered::<Entity, With<AnimationTarget>>()
.iter_mut(world)
.collect();
for entity in animations {
world.entity_mut(entity).insert(SkipTween);
}
}
}
fn system(mut commands: Commands, query: Query<(Entity, &mut TimeRunner)>) {
query.iter_mut().for_each(|(e, t)| {
t.set_paused(true);
commands.entity(e).insert(SkipTimeRunner);
})
}
SkipTween is used with individual tween entity. Insert them to a tween will skip them from tweening.
Do I have to use set_paused and SkipTimeRunner both?
set_paused only pause the timer but do not disable tweens.
So depends if you want to edit tweening target’s value too.
Edit: Forgot to mention that leaving it them active also have performance overhead for doing nothing
Cool, tested and it worked! Thank you @wooden zinc
Quick question: Does Time<Virtual> pause tween animations?
I’ve never tested this but all systems use Res<Time>.
According to the documentation, Res<Time> is backed up Time<Virtual>, so theoretically, I could pause all animations via that. I'll try that and report back what I found
Hmm... when I pause Time<Virtual> animations powered by bevy's AnimationPlayer are paused (including all shaders that rely on globals.time; but not those powered by bevy_tween crates. I have to pause TimeRunner and add SkipTimeRunner to pause those. That seems strange. Maybe I was doing something wrong...
That’s weird. I’ve looked through animation_player and they used exactly the same resource
@cunning heath Res<Time<Real>> is used in the ticking system… I have no idea why, when, or how I put that there but the system definitely doesnt suppose to use that
That is good to know. Are you planning to change that into Virtual in the next release?
Yep, fyi all we had to do is change this https://github.com/Multirious/bevy_time_runner/blob/main/src/time_runner.rs#L415
Does this crate work with 3d or just 2d?
With 3d too
Been a while since anyone needed help with tweens I suppose :P
Trying to figure out how to do a partially repeating tween. I have a sequence that fades in a text and then pulses the text and I want to only repeat the pulse.
So far I've tried spawning these effects as two separate animations and playing around with go/forward/backward but I haven't figured out the latter and the former is clunky because it's two animations running at the same time and modifying the same component.
You can use TweenEvent to extends functionality. By adding a custom event at the end of the animation then register a custom system that jump the animation back to that specific time-frame, you essentially create a partial repeating animation. Be sure to not use Repeat option so they don’t interfere with the custom repeat logic. Note that foward/backward/go does not modify the animation timing while it is running. It only change the timing parameter in which a tween start and end during creation. I decided to use those name so it is consistent, eg. “forward” instead of “delay” because I can’t find for other better names that’s consistant with the others.
Also you asked help at quite the right time😄I was a bit busy with school for the past few months so I had zero activity on the project but now I’m in school holiday so I can continue (but also might take awhile since i had another project in progress).
Hmm is this crate still needed? The 0.15 animation might be actually good enough without the need for 3rd party crates.
Though I'm not really sure since I haven't try to work with them yet.
I've tried it out, and while there are great improvements it's still quite a few steps away from truly replacing tweening crates IMO.
I've taken another look around the ecosystem but IMO, bevy_tween is still the most ergonomic and powerful tweening library I've tried so far. I really hope that the upstream devs would take a serious look at it while developing their animation API further.
Great to know. I'll continue to support later version then. I think I'd replace the interpolator trait with the shiny new animatable property and easingfunctions.
Is there a way to not reset the animation at the end of a non-looping animation? I want to translate an entity from one point to another, but the position is always reset to the beginning at the end of the animation.
I believe that shouldn't happen?
It works now, must've been cosmic rays 😂
@wooden zinc how does bevy_tween implement the animation on StandardMaterial? When I was manually updating the material color in bevy, it has significant performance impact on iOS (desktop seems fine). It seems to me bevy_tween's animation on StandardMaterial is reasonably fast. I'm curious about how you implemented it. Thanks
I don’t think I have any implementation on StandardMaterial. Have you implemented it yourself by any chance?
I'm using
.add_tween_systems((
asset_dyn_tween_system::<StandardMaterial>(),
asset_tween_system::<EmissiveColor>(),
))
impl Interpolator for EmissiveColor {
type Item = StandardMaterial;
fn interpolate(&self, item: &mut Self::Item, value: f32) {
item.emissive = self.start.lerp(&self.end, value);
}
}
My question is mainly about how frequently do you update the material's emissive color
It’s every frame if the animation is running.
you might need to do a few spot benchmark. I’m pretty sure I’ve not done any special for different interpolators.
Do you update it in the Update schedule?
Feature request: Updating material's uniforms can be quite expensive, esp on mobile platforms. It would be great if bevy_tween offer a customization to allow it run skip frame instead of every frame.
Will this crate be updated for 0.15 soon?
Should be ready in about tomorrow
nice thanks 🥳
I believe you could strap a run condition on the system directly.
How do I do that based on the following example
commands.animation().insert(animate_death_color(
target,
Duration::from_secs_f32(duration),
));
fn animate_death_color(
target: TargetAsset<StandardMaterial>,
duration: Duration,
) -> impl FnOnce(&mut AnimationCommands, &mut Duration) {
sequence((
tween(
duration.mul_f32(0.4),
EaseFunction::Linear,
target.with(EmissiveColor {
start: LinearRgba::new(0.0, 0.0, 0.0, 0.0),
end: LinearRgba::rgb(10.0, 1.0, 1.0),
}),
),
tween(
duration.mul_f32(0.6),
EaseFunction::Linear,
target.with(EmissiveColor {
start: LinearRgba::rgb(10.0, 1.0, 1.0),
end: LinearRgba::rgb(10.0, 10.0, 10.0),
}),
),
tween(
duration.mul_f32(0.1),
EaseFunction::Linear,
target.with(EmissiveColor {
start: LinearRgba::rgb(10.0, 10.0, 10.0),
end: LinearRgba::new(0.0, 0.0, 0.0, 0.0),
}),
),
))
}
Since bevy_tween works by you having this asset system registered, you can attach a run_if condition to it. It won’t have any issue regarding timing since that is calculated by another system.
Bevy powered™️
In the future version the system registration will be a plugin instead. Seems like this is a useful case, I’ll add a simple configuration option for the plugin too.
Are you suggesting add inthe run condition here?
app.add_plugins(DefaultTweenPlugins)
.add_tween_systems((
asset_dyn_tween_system::<StandardMaterial>(),
asset_tween_system::<EmissiveColor>(),
))
Yep
Like this?
.add_tween_systems((
asset_dyn_tween_system::<StandardMaterial>().run_if(skip_frame_check_here()),
asset_tween_system::<EmissiveColor>().run_if(skip_frame_check_here()),
))
Yeah, that looks good.
Nice. Let me try that. Thanks @wooden zinc
@wooden zinc tested and it worked. However, this approach comes with some limitations: If the animation runs with duration of let's say 1.0 sec and I configured it to run every 0.1 sec. Then it depends on when the animation starts. I could end up lose the last frame update if the animation start at 0.05 sec mark. So the end value of the animation may end up not matching the actual end value specified. This is less an issue for long duration animation but is more likely to happen for short duration animations
Well thats unfortunate
I dont like that
Im gonna have a long and hard think in the toilet
lol, sorry about that. I guess the tween_system is looking up the animation config and update the object. When animation config finishes, it's removed from the game, so the next time the tween_system runs, it can't find the animation so end up not doing the update right? I wonder if it is possible to remove the animation config after tween_system successfully update the object
Yeah, that would require some changes internally. In the mean time, one possible solution is you could configure the run condition to execute if it detects any animation ending which would resolve the issue. It may not be as performance if there are many multiple animations ending near each other.
That might work. Does bevy_tween emit/trigger any event when an animation finishes? It would be easier if that is the case; Otherwise I have to maintain the animation status inside the game logic
Yep, though not bevy_tween directly. TimeRunnerEnded is emitted every time the timing is at the end of the timeline. The animation config is still available until the next frame.
@cunning heath Another solution: Instead of animating on the value directly, animate an intermediate component to store the latest value. You then can update the material whenever you want. Might have more granularity and simpler to reason about. Wouldn't require internal change too
@wooden zinc that is a good idea! Is it like creating a ProxyAnimation object and use bevy_tween to animate that proxy directly without slowing down. Then I can create a custom update system at a slower pace to update the actual animated object (material) and then do the cleanup after it's finished?
I think so! Though ProxyAnimation "object" isn't required. I think it's simple as just have a proxy component in the same entity. A cleanup is also wouldn't be needed afaik. Just leave the component as is since removing it will require archetypcal changes which could be a performance problem at scale.
Yeah, I mean adding a Proxy component in the animating entity. I'm thinking about having
#[derive(Component)]
struct Proxy {
value: f32,
end: f32,
done: bool,
}
and let bevy_tween to update the value in it. Then have the following system
fn update_ui_once_every_10_frames(...) {
if proxy.done { return }
if value == end {
proxy.done = true;
}
// update object with current value
}
to update the entity. Without removal, I feel I need to maintain a done state to mark the last update.
BTW, you mention removing component at scale may cause performance issues? How bad could that be? I'm using a lot of that in my game. I wonder if I need to change those into a bool based component.
Removing and inserting components frequently can cause table fragmentation and reduce cache locality. There's no definitive way to tell if it has a big impact other than benchmarking. If it do has a significant impact then you can consider trying out sparseset and benchmark again. I think the done state is fine though there's other way to check like utilizing change detection but I'm not sure what are the performance profile differences.
Instead of checking done status based on the end value, I think it might be better to rely TimeRunner.is_completed() to check if the tween completes. Besides that, everything works pretty well. @wooden zinc
poll_question_text
Do you guys use TweenEvent for events that do not implemented clone? We’re considering adding a clone bound to TweenEventTakingPlugin to add support for observer or deprecating the thing entirely if unused. If the latter is the case, then we might remove uneccessary Option inside TweenEvent.
victor_answer_votes
1
total_votes
1
victor_answer_id
2
victor_answer_text
Nah
@wooden zinc Sup, trying out your crate :). Is there a reason why AnimationTarget can't implement Default? Unfortunately, when setting it as a required component, i have to do this: AnimationTarget(|| AnimationTarget). Which feels kinda silly lol. But something with 0 fields implementing default also sounds silly, but at least helpful in some cases.
Oh it's simply that no one has ever requested that before.
thats what i figured lol
Are there any specific reason you want to use this required component? You're usually supposed to used it in conjunction with the .animation() builder, not exactly required by any other components.
In an example i have seen, u spawn it with the other components for that entity. As far as i understand, u really just set that component once on something u want to animate, right? It doesnt need to change after that. I have something that I want to be animatable, so im adding it as a required component so i avoid the error of forgetting to add it.
Got it. This should be added by the next release in any case.
U mean deriving Default on it?
Yep, it should be simple as that.
OK cool, ty 👍
@wooden zinc Im trying to get asset tweening to work. Right now im giving it the entity that has the MeshMaterial2d handle. Not sure how it works internally to get to the asset.
I threw a print statement in the interpolate method but its not showing up, is that a sign that its not even running?
//adding it like this
app.add_tween_systems(bevy_tween::tween::apply_asset_tween_system::<CardMaterialTween>);
//interpolator
#[derive(Debug, Default, Clone, PartialEq)]
pub struct CardMaterialTween {
pub start: Vec2,
pub end: Vec2,
}
impl CardMaterialTween {
pub fn new(start: Vec2, end: Vec2) -> CardMaterialTween {
CardMaterialTween { start, end }
}
}
impl Interpolator for CardMaterialTween {
type Item = CardMaterial;
fn interpolate(&self, item: &mut Self::Item, value: f32) {
println!("oapjdopasjdop");
let result = self.start.lerp(self.end, value);
item.rotate(result.x, result.y);
}
}
What does your animation code looks like?
commands.entity(entity).animation().insert_tween_here(
Duration::from_millis(100),
EaseKind::BackOut,
(TargetComponent::marker().with(CardMaterialTween::new(
cm.xy_rot(),
Vec2::new(x_diff, y_diff),
)),),
);
You have to use bevy_tween::tween::TargetAsset to work with assets.
i figured i was missing something like that, lol. Let me try. and ty 🙂
.into_target() on the handle also works.
@wooden zinc If i add 2 animations with 2 separate commands to the same entity, should both work fine without any weird effects?
Two completely different animations (i.e Translation and Asset)
nope
If you use insert_tween_here there’s a chance some previous component my get replaced
If you use insert, new children will be made.
I see, i think i can switch to insert and avoid the weirdness im seeing. Thanks 🙂
hmm i realized i was not clear enough in the above respond
the latter method may still interfere with other animations, so to be more precise:
.insert_tween_heremethod. This inserts everything into the entity you’re inserting to. It’s not supported for multiple animation running simultaneously because the working components may get replaced by the previous call..insert(directly on the target entity). This can be used to run multiple tweens simultaneously but not multiple animation. Because TimeRunner will be replaced in the target entity when called multiple times. The tween children will actually still get created so it may seems like the animation is “merging”..insertbut as a new children of the target entity (target_entity.with_children(|c| { c.animation(); })). Animations will truly be separated from each other at this level because each TimeRunner gets to be its own entity.commands.animation()this will never causes any interference to other animations due to it spawning a new TimeRunner entity each time.
Thank you for the clarification!
does anyone need animation blending?
For example, if you have a UI transition between menu and if the user switch between them quick enough then animations will be blended together.
Not currently, but it does look like something that'd be nice to have.
Exploring macro design for creating state
let sprite_commands = commands.spawn(…);
let target = sprite_commands.id().into_target();
let target = tween_state!(target:
tween::translation = Transfrom::default(),
tween::color = Color::WHITE,
);
sprite_commands.animation()
.insert(sequence((
target.translation_to(Vec3::new(...), ...)
target.color_to(Color::BLACK, ...),
)));
and
define_tween_state! {
MySpriteState:
tween::translation,
tween::sprite_color,
}
let sprite_commands = commands.spawn_empty();
let mut target = MySpriteState::target(sprite_commands.id().into());
sprite_commands
.insert((
Sprite { ... },
Translation::default(),
).fill_state(&mut target))
.insert(sequence((
// ... same as above
)));
I went over most of the crate and I'm impressed. There's one thing I can't wrap my head around - how should I respond to tweens being done?
From what I understand, listening to TimeRunnerEnded should cover all done tweens, and they carry the entity so I can get its EntityCommands easily. I could put a marker component on it to indicate what should be done with it when the tween is done. But what if there's more than one ongoing tween?
Is there a way to assign a "TweenDoneEvent" by tween?
I believe it’s not that useful for every single tween to have a finished event so there’s no event for it at the moment. You can manually add TweenEvent after the tween which would cover most of the cases down to specific data type.
Oh I missed that
Thanks!
Listen
Installing this crate was probably the smartest desicion I've made since I began messing with bevy about a year ago
I tried it on a piece of code and it made it so clean and pretty that I almost shed a tear
that's amazing 😂
Is there a quick way to get to all the tweens that currently have this entity as a target other than querying over all the tweens or making a marker component that includes the entity in it?
Unfortunately nope. bevy_tween currently doesn’t index what tween is animating which entity. Though, it seems like it would be useful for debugging. Also, I think I forgot to include the tween entity ID in the error message 😬
Fyi, if you want/need to do that now, it’s simple as add an observer to index fresh Tween<TargetComponent,I> component and then you can get its target entity.
I'm not very happy with my code, sharing it here in case other people would want to use it:
#[derive(Debug, Clone, PartialEq, Eq, Hash, Component)]
pub struct TweenTarget(pub TargetComponent);
pub struct TweenTargetPlugin;
impl Plugin for TweenTargetPlugin {
fn build(&self, app: &mut App) {
app.add_observer(
|trigger: Trigger<OnAdd, ComponentTween<Transform>>,
mut commands: Commands,
query: Query<&ComponentTween<Transform>>| {
let mut tween_commands = commands.entity(trigger.entity());
let tween = query.get(trigger.entity()).unwrap();
tween_commands.insert(TweenTarget(tween.target.clone()));
},
);
}
}
Hmm I was thinking more of a HashMap<TargetComponent, HashSet<Entity>>
where the key is target and value is set of tweens kind of index.
@quartz locust does this works better for you?
(thinking)
I'm experimenting right now, will let you know if something better comes to mind
impl Plugin for TweenDestroyerPlugin {
fn build(&self, app: &mut App) {
app.add_observer(
|trigger: Trigger<OnRemove, AnimationTarget>,
mut commands: Commands,
mut query: Query<(&mut ComponentTween<Transform>, Entity)>| {
let entity = trigger.entity();
for (mut tween, tween_entity) in &mut query {
match &mut tween.target {
TargetComponent::Entity(target) if *target == entity => {
commands.entity(tween_entity).despawn_recursive();
}
TargetComponent::Entities(targets) => {
targets.retain(|&target| target != entity);
if targets.is_empty() {
commands.entity(tween_entity).despawn_recursive();
}
}
_ => {}
}
}
},
);
}
}
@wooden zinc what do you think of this
what an epic plugin name
Not ideal, but I don't think there's a way to escape giving ComponentTween a type
I guess the best thing I could do is make the plugin generic
lmao
The Destroyer Of All Tween-kind
The thing is
TargetComponent isn't generic as itself
I wonder what would be the cost of separating it from the ComponentTween and making it a #required component to go along with it
So you are trying to clear any animation when animation target component is removed right?
I'm trying to despawn the tweens themselves, or remove the entity from the tween targets if they have more than one
A better approach would be to destroy if after removing it from the entities we're left with none
Hold on
I added that
This is the plan for future release (not with required component). Separating the thing into components is likely a requirement to reduce combinatorial explosion when supporting bevy curves.
I'm still not quite sure what is the end goal 😅 but I have some idea for specifically the code.
If you were talking about improving the code performance, this code would have worst-case O(n) because of for loop here.
If we index the tweens and targets into HashMap<TargetComponent, HashSet<Entity>> with On<Add, ...>first, the time-complexity would be O(1) when we are going to remove those tweens.
If you just want to reduce the amount of times you have to register the plugin because of the generics, you can improve it by wrapping the component_tween_system inside a custom plugin that would also register your TweenDestroyerPlugin. So you only have to specify your components once instead of twice.
You can't escape generics.
I have some code to despawn tween without dealing with generics. It's not the best since it's a regular system and not observer, but observer would despawn the tween before applying it. Maybe you'll find it helpful @quartz locust
UPDATE: I made some changes to the code, I don't know what I was doing with the original lol
// marker component, either attach to the tween or animation target to despawn on finish
#[derive(Component)]
pub struct DespawnOnFinish;
pub fn despawn_tween_on_finish(
mut time_runner_ended_reader: EventReader<TimeRunnerEnded>,
mut commands: Commands,
tween_query: Query<(Option<&Parent>), (With<DespawnOnFinish>, With<TimeRunner>)>
) {
for event in time_runner_ended_reader.read() {
let tween_entity = event.time_runner;
if let Ok((possible_parent)) = tween_query.get(tween_entity) {
// remove from parent hirerarchy if any
if let Some(parent) = possible_parent {
commands.entity(parent.get()).remove_children(&[tween_entity]);
}
commands.entity(tween_entity).despawn_recursive();
}
}
}
and just configure it to run after the apply tween to animate the last frame.
app.add_systems( PostUpdate,
despawn_tween_on_finish
.after(TweenSystemSet::ApplyTween)
);
Sorry for the delay (haven't got much time for hobbies lately)
I ended up going with:
use crate::{plugin_for_implementors_of_trait, prelude::*};
plugin_for_implementors_of_trait!(TweenDestroyerPlugin, Sendable);
impl<T: Sendable> Plugin for TweenDestroyerPlugin<T> {
fn build(&self, app: &mut App) {
app.add_observer(
|trigger: Trigger<OnRemove, AnimationTarget>,
mut commands: Commands,
mut query: Query<(&mut ComponentTween<T>, Entity)>| {
let entity = trigger.entity();
for (mut tween, tween_entity) in &mut query {
match &mut tween.target {
TargetComponent::Entity(target) if *target == entity => {
commands.entity(tween_entity).despawn_recursive();
}
TargetComponent::Entities(targets) => {
targets.retain(|&target| target != entity);
if targets.is_empty() {
commands.entity(tween_entity).despawn_recursive();
}
}
_ => {}
}
}
},
);
}
}
Which works with tweens of types I register on another file:
plugin_for_implementors_of_trait!(SendableGenericPlugins, Sendable);
pub struct GenericPlugins;
impl Plugin for GenericPlugins {
fn build(&self, app: &mut App) {
app.add_plugins((
SendableGenericPlugins::<Translation>::default(),
SendableGenericPlugins::<Scale>::default(),
));
}
}
impl<T: Sendable> Plugin for SendableGenericPlugins<T> {
fn build(&self, app: &mut App) {
app.add_plugins(TweenDestroyerPlugin::<T>::default());
}
}
I have a new goal: being able to tag animators with "my tweens kill all previous tweens of this type on the entities they affect" or "if my tweens entities have an active tween of this type, do not activate this tween"
Here how I listen tp them:
triggering_entity: Entity,
tween_policies_query: Query<&TweenSpawnPolicy>,
tweens_query: Query<(&ComponentTween<T>, &Parent, Entity)>,
mut commands: Commands,
) {
if let Ok((tween, parent, tween_entity)) = tweens_query.get(triggering_entity) {
if let Ok(tween_policy) = tween_policies_query.get(parent.get()) {
... //I never get to that part
}
}
}
Here's how I insert it:
orb_query: Query<(&Transform, Entity), With<Orb>>,
mut commands: Commands,
) {
for (orb_transform, orb_entity) in &orb_query {
if let Some(orb_animation_target) = commands
.get_entity(orb_entity)
.map(|e| e.id().into_target())
{
let collection_duration = Duration::from_secs_f32(ORB_COLLECTION_DURATION);
let orb_z = orb_transform.translation.z;
let mut orb_transform_state = orb_animation_target.transform_state(*orb_transform);
commands
.spawn((
TimeMultiplierSubscriber(TimeMultiplierId::GameTimeMultiplier),
TweenSpawnPolicy::DestroyOthersOfTypeOnAddition,
))
.animation()
.insert(…);
}
}
}
Could it be that I don't understand the tween hierarchy well?
I could make the repo public and send a link here if it helps
So here you have a time runner on each tween?
What about events? And when do you despawn the animator?
If you are refering to the event TimeRunnerEnded, it's a built-in event to the crate. It is sent automatically when the TimeRunner timer is finished. I'm not sure about the animator, I mostly just use time runner + multiple tweens like this
(TimeRunner, DespawnOnFinish)
- child 1: ( EaseKind, TimeSpan, TargetComponent, etc.)
- more tween children
The tween children run on their own time span and are despawned along with the TimeRunner when it is finished. Having a TimerRunner on each tween is also valid too.
This is the recommended method most of the time. You can also see it in one of the examples
But it do not provide any more granularity other than despawning the whole animation which seems like it is the thing you were trying to do (?)
Yeah, it's for cleaning up finished animation or despawning entities after some duration.
The thing is
I have an animation over a sequence of tweens with an even that shoots once they're all done
However
Done TimeRunners despawn the animation recursively, resulting in the event at the end of the sequence never firing
Although
Listening to done TimeRunners in an event listener instead of an observer might do the trick as it won't execute instantly
Yup
That did it haha
Had some rewrite pushed to branch overhaul-v2. Barely had any documented but you can test/review if you guys want.
The new design features:
Core:
- [Done, can be tested but input might be changed] Arbitrary system parameters for an
Altersystem. (New trait and system to replaceInterpolate) - [Done, can be tested] Arbitrary curve that implements the curve trait
- [WIP, it's always blending atm] Contradicting tweens solving methods
- [todo] Dynamic tween that supports animated property
- [Done, can be tested] Detect and warn if a certain system required for generic tweening is not registered for a type.
Framework: - [Done, can be tested] Better traits and methods. Building currying function should no longer be required for building animation with state.
- [Done, can be tested] BuildAnimation trait instead of Fn(…) to provide more flexibility. (struct and enums can be provided which should allow for further bulk operation like setting ease for the whole sequence)
Bunch of features from the previous is still missing.
Oh cool! I have a newish test project that I can try this out on
Does this support animating resources yet?
This is how you would currently implement one, I think
use bevy_tween::tween_core::{AlterPlugin, AlterResource};
struct AlterMyResource;
impl AlterSingle for AlterMyResource {
type Value = f32;
type Item = MyResource;
fn alter_single(...) {...}
}
fn main() {
App::new().add_plugins(AlterPlugin::<AlterResource<AlterMyResource>>::default());
}
Cool! Btw, is there a typo there or in the code? That plugin seems to be called AltererPlugin
Oh I just updated that recently to AlterPlugin
Like in the last 30 minutes?
I'll refresh
Back from my errands and trying to implent this, I'm clearly doing something wrong:
<bevy_tween_core::alter::AlterResource<bevy_game::input::InputData> as bevy_tween_core::alter::Alter>::alter_system could not access system parameter Res<TweensTargetFinalValue<AlterResource<InputData>>>
Is this a resource I must add manually somewhere?
Oh that were from a wip commit I've pushed. You might wanna stick to just this commit dd68d287f8b38bd0d681c0368d64b6038e78e767 for now
I didn't mean to do that but my muscle memory got ahead of me 😅
Ah okay. I'll switch to that. Does the rest of this look fine?
app
// snip
.add_plugins(DefaultTweenCorePlugins)
.add_plugins(AlterPlugin::<AlterResource<InputData>>::default())
.insert_resource(InputData::new())
// snip
;
//------------------------------------------------
#[derive(Resource, Debug)]
pub struct InputData {
pub mouse_pos: Vec2,
pub mouse_down: bool,
pub radius: f32,
}
impl AlterSingle for InputData {
type Value = f32;
type Item = InputData;
fn alter_single(item: &mut Self::Item, value: &Self::Value) {
item.radius = *value;
}
}
//-----------------------------------------------
Yeah that's seems good
If there were any problem, the debug plugin should notify you of missing plugin and the compiler would complain of missing trait.
Hopefully
I wonder if how expensive it would be to automatically add plugin on the fly. It’s possible but could be costly because this had to be checked every time on add. Though one could find it useful for quick iteration.
Alright it compiles in that commit
Soo how do I set the tween? Is it the same as with the main branch?
Mostly, they’re still components but just wrapped behind a generic one
These are in bevy_tween::tween_core::argument
It remove component requirement from the inner type and I can do interesting stuff with generic component hook
Ah but hey, resources don't have components
What do you want to set?
Tween the radius
Youve said set the tween, whats the thing you’re trying to modify? The timespan, the curve, or something else?
Oh the timespan
That will be bevy_tween::time_runner::TimeSpan and it is still a component
Oh alright then. Well the bevy_tween’s design philosophy is all animation parameters is components.
When you’re animating resource
There’s a component somewhere telling the system on how to animate that resource
So you're creating an entity for each animatable resource?
Not exactly, it’s an entity per animation.
You can try to use bevy-inspector-egui to check what is the exact hierarchy looks like when dealing with one.
Alternatively also check out bevy_tween_core test that build the entity animation manually component by component.
Oh wait. You usually have to declare a unit type specifically and implement AlterSingle on that. See bevy_tween_core::alters::transform for examples. This will probably still work but some trait might be missing. Sorry about that, I probably should to go sleep already.
Aha, okay
So I define a struct for the field I want to tween:
pub struct AlterInputDataRadius;
impl AlterSingle for AlterInputDataRadius {
type Value = f32;
type Item = InputData;
fn alter_single(item: &mut Self::Item, value: &Self::Value) {
item.radius = *value;
}
}
And add that as a plugin:
.add_plugins(AlterPlugin::<AlterResource<AlterInputDataRadius>>::default())
I don't see any generated entities with the inspector though.
Have you spawn any animation?
I still don't understand how to do this for a resource
I'll reread the source files you referenced, I found them a bit opaque at my skill level
pub struct Blend {
pub weigth: f32,
pub additive: bool,
}
``` found a typo : p
I have made this typo like 5 times already
Alright, I think I understand your question now.
To put it simply…
Yes, starting animations (tweening to target values) for individual fields on a resource is my goal
I completely forgot to make a tweenbuilder method for spawning resource target in the first place. Thank you for being patient 😂
Ah 😄 no worries, glad to be of assistance
It should still be possible with the raw method
Don't let me stop you from getting some sleep haha
I'm in no hurry to get tweens working yet, just a fun detour for now
commands.spawn(
TimeRunner
TimeSpan,
Target(()),
Alterer(AlterReseouce(AlterInputDataRadius))),
Curve(EasingCurve::new(…)),
);
Something like this should get it working
It’s fineeee
😂It’s not like im procrastinating or something…
I'm not sure you're in typing condition anymore 😆
okay fine youve got me. here come my bed
Sweet coding dreams 
After trying for awhile, I’m not sure if I should implement value collection system? (random technical abstract name I made for blending). It requires quite a bit of indexing code, hashmaps, and clones that adds some amount of overhead.
Maybe the library is already have many battery included that maybe this doesn’t matter?
Though this is optional and there is fine grain control down to entity level to choose which tween to be indexed. Once indexed, all alterer of the same type is guaranteed to be executed once per target and so they can be parallelized. You can debug on which tween is doing what to what. You can choose whether you want to blend, warn, or anything else custom for contradicting tweens
You mean summing up the values like if I had two tweens telling an entity to move the same distance in opposite directions, the entity would stay in the same place?
You can tell them to do that, yes.
(nods)
Wait, so you're not going to impleemnt that but we'd be able to do that ourselves
I mean, the users
There'll be some default method to choose, that includes the blending that bevy_animation implements.
Cool, I didn't know they do that
Btw, do you know if they have a built-in frame animator?
I ended up making a funny one like:
type Item = Sprite;
fn interpolate(&self, item: &mut Self::Item, value: f32) {
let current_handle_index = (self.frame_count as f32 * value).floor() as usize;
if let Some(sprite) = self.sprites.get(current_handle_index) {
item.image = sprite.clone();
} else {
print_error(
format!(
"FrameAnimator: {:?} has no frame at index: {:?}",
self.name, current_handle_index
),
vec![LogCategory::RequestNotFulfilled],
);
}
}
}
pub fn frame_animation_maker(animator: FrameAnimator) -> impl Fn(&mut Sprite) -> FrameAnimator {
move |current| {
if let Some(first_frame) = animator.head() {
current.image = first_frame;
}
animator.clone()
}
}
I think bevy_animation supports generic curve (dynamic) but I didn't play with them much to know the exact details but I think that should be possible.
I mean, this works well, I just think it's funny that I'm building an animator over a tween that's built over an animator
I think like
In the next version you'll be able to build this kind of interpolator that interact with other components directly.
The main difficulty I'm having with Bevy is that sometimes there might be a function somewhere that does exactly what I'm looking for and I'm just unaware of its existance
Oh wow
💪
Yeah, it's kind of system inside a trait.
Though, I probably should mention that this wouldn't be finish for awhile since I ended up enjoying procrastinating this project by making EDM music 😅.
Is there an example anywhere of sequences of arbitrary length? I'm trying to have a sprite follow a path, but the length of the path can vary. I've tried mapping the path points to a vec of translation_to tweens and then passing that into sequence, but haven't had much luck
Hmm, we might need to get a bit more specific.
Does the length of the path change with time (or vice-versa)? Were the path supposed to stop somewhere or would the path will be constantly changing shape/length?
Are the path straight or instead spline-like?
Also maybe the “follow” example can be repurposed from cursor to path which would also work. This doesn’t require any kind of sequence .
It’s a turn based strategy game, where sprites move over a square grid following an a-star algorithm. So depending on how far away a target the player chooses, the path could have a varying number of waypoints
I wasn’t planning on smoothing those waypoints out into a spline
basically something like (doesn't compile)
commands
.entity(*figure_entity)
.animation()
.insert(sequence(
path.into_iter().map(|point| {
tween(Duration::from_secs_f32(0.3), EaseKind::Linear, translation_to(point.extend(1.0)))
}).collect()
));
basically, is there a way to put an iterator, loop, or vec of tweens into a sequence
You can create your own function/combinator which lets you access the inner animation commands and build animation imperatively.
fn path_animation(path: SomeType) -> impl FnOnce(&mut AnimationCommands, &mut Duration) {
move |a, pos| {
path.into_iter().map(|point| {
tween(...)(a, pos)
})
}
}
commands
.entity(*figure_entity)
.animation()
.insert(path_animation(path));
Something like this should suits your needs
Using the closure directly is also perfectly fine.
(I think that's pretty cool)
It is
I have that one
duration: Duration,
interpolation: I,
tween: T,
spawn_policy: TweenSpawnPolicy,
) -> impl FnOnce(&mut AnimationCommands, &mut Duration)
where
I: Bundle,
T: Bundle,
{
move |a, pos| {
let start = *pos;
let end = start + duration;
a.spawn((
TimeSpan::try_from(start..end).unwrap(),
interpolation,
tween,
spawn_policy,
));
*pos = end;
}
}
Though if I ever need more components added to the tween I might just make a generic tween_with_component that takes a <C: Component>
let mut entt_cmd = cmd.spawn((
AnimationTarget,
Sprite {
custom_size: Some(Vec2::new(100., 100.)),
..default()
},
Transform::from_xyz(-400., -400., 0.),
));
let target = entt_cmd.id().into_target();
entt_cmd.animation().insert_tween_here(
Duration::from_secs_f32(1.),
EaseKind::Linear,
target.with(translation_to(Vec3::new(400., 400., 0.))),
);
i don't know what i did wrong, but i don't think it moves
Seems good to me. Have you registered the plugins?
Wait
translation_to requires a mutable state but you're not declaring any.
have you try bare translation first?
what do you mean by "mutable state"? i don't think I've read them in the docs
bare translation doesn't work
https://docs.rs/bevy_tween/latest/bevy_tween/#state this section right here should show you how to use one
Getting started
Could you try running any of the examples from the crate just to make sure that everything is running correctly.
i can finally touch my code again
this works
thanks!
somehow reading that the first time one caught the sprite_color_to and not the .state part
now im curious why Interpolator::interpolate is &self and not &mut self
Is there a build-in way to do something like:
move |a, pos| {
let start = *pos;
let end = start + duration;
a.spawn(TimeSpan::try_from(Duration::from_secs(0)..duration).unwrap());
*pos = end;
}
}
Maybe I should've called it just delay()
what is this supposed to do?
like forward()?
It's to make sure that bevy will parallelize sampling the values.
Though I think they're not being utilized much here.
I have some built in systems around tweens that I want to use for regular timers
So I thought I should just make an animation() that does nothing but send an event when it's done
So I needed something that would tell it to fire an event only after a predefined amount of time (that can change as an animator in my code can subscribe to a time-scaler that might change during the game
yeah there’s no builtin for this
What do you think of this solution?
Is there a smoother way to achieve that?
I think its fine. It’s just like a very minimal tween. Though, some memory will be wasted but just very slightly that shouldn’t be a problem.
(Unless you have like a million of these)
I might 😅
Welp, better get to profiling then!
Something is funny
Some(next_color) => parallel((
transform_change_tween,
tween(
animation_duration,
EaseKind::ExponentialIn,
color_state.with(sprite_color_to(next_color)),
),
)),
None => transform_change_tween,
};```
Is there no way to make a conditional animation?
Like, I want to animate the color too only under certain conditions
hmmmm
Like, an enum where one variant wraps a parallel and another wraps a regular tween
right?
Worst case I'll just spawn a redundant tween for the color
This is probably ain't possible since you need to implement FnOnce on the enum
but that's nightly
@quartz locust Instead of returning a closure you could try passing arguments manually instead
Might work if you have AnimationCommands available
|a, p| {
match ... {
... => tween1(a, p),
... => tween2(a, p),
}
}
Thinking of making bevy_tween a subcrate of bevy_timerunner instead.
This would make a lot more sense for how things are represented currently.
.animation() builder is not really a special thing to animation but more about bevy_timerunner’s thing
There’s quite a bit more breaking changes too so I guess Id get a chance to change the crates name to prevent further confusion with bevy_tweening
Then if stuff is properly in place then maybe I could starts adding audio support
I think combining the two crates into a single workspace/monorepo sounds nice. Makes contribution easier.
Does using runner.set_tick(0.0) is a correct way to replay an animation?
What's the pattern for having multiple tween and being able trigger them?
Yep, that should be fine in most cases.
There's currently no default way to enable a pre-spawned tween if that what you meant. It's currently expected for user to make their own at the moment. Usually with events or some component system.
I've been really enjoying this crate, thank you! ❤️
Same!
I just made another tiny crate with this crate haha
hello ! first time using animation/tween crate.
i tried to do this:
let mut transform_target = AnimationTarget.into_target().transform_state(transform);
ec.animation().insert(parallel((
tween(
Duration::from_secs(1),
EaseKind::Linear,
transform_target.translation_by(Vec3::Y * 20.),
),
tween(
Duration::from_secs(1),
EaseKind::CubicIn,
transform_target.scale_by(Vec3::splat(0.5)),
),
tween(
Duration::from_secs(1),
EaseKind::CubicIn,
AnimationTarget
.into_target()
.with_closure(|c: &mut TextColor, v: f32| {
c.0.set_alpha(255. - v * 255.);
}),
),
)));
the first two works as per the example, but the custom modification doesn't work, the closure is never called.
i tried using a state, but i couldn't use .with_closure and only .with.
thanks
i am obligated to impl a Interpolator trait ?
i tried implementing the interpolator trait on a struct and using it but still not working
I think this is because of the alpha of Color in bevy is ranged from 0.0 to 1.0 (instead of 0.0 to 255.0)?
Have you registered the associated dynamic tween system?
ohhh right, but still not working, the closure is not being called
ive tried when i tried to use a Interpolator struct, but didn't work (im sure its me who didn't do it correctly).
is it required to have a Interpolator struct for the animation ? whats the purpose of with_closure then ?
thanks
Interpolator is implemented for Fn(…) so you can use closure for quick prototyping if needed. You’d implement interpolator for custom struct if you wanted more rigid code.
Anyways, closure not being called is usually because a system is not registered correctly, could you show me the tween systems youve registered?
yep it works now...
using the component dyn
i was using the component_tween_system before with the interpolator trait, i thought the dyn ones where for other advanced things lol
thanks a lot
what does registering a component internally does ? it can't be detected from the closure or target type ?
When you’re using closure, the type it represents in the ECS world is different from a rigid type because it’s a Box<dyn Fn(…)> wrapped inside Box<dyn Interpolator>. component_tween_system will look for interpolator type directly and different interpolator for the same component may do different thing and so will require multiple registrations.
component_dyn_tween_system is only required to be registered once for the same component since it do not look for a rigid type but things that implements Interpolator.
hello, i want to implement a arcing to my projectile (simple archer troops 2d arrows), and i guess i can do it with this crate ? a tween from the shooter to the target ?
i think i saw in the example how to dynamically change the end state (if the target of the projectile move)
but how can i do a arcing animation ?
i use a certain distance per seconds to move the arrows, can it be used ?
or im better of doing it manually myself ?
thanks
You could create a wrapper for Vec2D (say, struct ArrowMover) and make a costum tween for it
ill try thanks
Hello. I'm using bevy_tween to move a player camera horizontally across terrain that has some amount of height variation, and I would like to keep the height of the camera adhered to the terrain height. Rather than creating a very complicated tween, I figured I could simplify things by just using a tween for the horizontal movement, and then setting the camera height to the terrain height every frame after the tween is applied:
app.add_systems(
PostUpdate,
terrain_correction_system.after(TweenSystemSet::ApplyTween),
);
fn terrain_correction_system(
mut positioned: Query<&mut Transform, With<Camera>>,
terrain: Query<(&TerrainTile)>,
) {
for mut transform in &mut positioned {
if let Some(terrain_tile) =
find_terrain(transform.translation, &terrain)
{
transform.translation.y = calculate_terrain_height(terrain_tile);
}
}
}
This works about half the time I run it, however the other half of the time there is a rapid jitter visually between the height I've set and the height the tween is setting, meaning there is some system ordering inconsistency. Does anybody know if there is some way to configure the system ordering so that the overridden height is always the transform that is rendered?
Would it work if you create an interpolator for exclusively mutating the horizontal position?
Otherwise you can order your system after TweenSystemSet::ApplyTween which by default is in PostUpdate
This is what I'm currently doing (terrain_correction_system.after(TweenSystemSet::ApplyTween)), and for some reason it is still inconsistent. I was wondering if there is some core bevy animation system or timerunner system that I need to order against as well.
I may have to dig into making a custom interpolator if I can't figure out the inconsistency, but its not straightforward as I do need the vertical animation sometimes. The above is a bit simplified, in actuality there is a 3d grid of blocks and the above logic is for other objects in a given space that affect height (ramps, half-blocks, etc).
Hmm, weird. That should be enough. Are you sure that system is in PostUpdate?
My terrain correction system is in PostUpdate, yes.
https://github.com/cxreiff/ratthew/blob/inconsistent/src/grid/animation.rs
Alright Ill try to dig into the code after this sleep.
Oh no pressure, only if you feel like it, I can certainly keep poking at it myself. Was just hoping there was some gotcha I was missing around the tween being "applied" versus the transform being modified or something. thank you regardless! It might be easier to look at one of the bevy_tween crate examples instead of my code, so I'll modify one of the examples to see if the same thing happens.
All good, I’m certainly curious too. Just that I planned the first thing to do in the morning is to take a look at that jitter 😄
Here we go, equivalent happening in the "follow" example:
https://github.com/cxreiff/bevy_tween/commit/db43005db18f600d30e114c9405fa730d8f06d2b
What does these example shows? It seems like they are working correctly.
Oh wait I get it now. Those 2 ran from the same code.
Yeah, same code, about fifty/fifty.
It’s currently correction_system.in_set(TweenSystemSet::ApplyTween). What happens of you change it to after?
Whoops. My bad. The example actually appears to work fine with my typo fixed.
It is correct as after in my original code though. That being said, I did actually find a fix for my specific problem: I created an interpolator for an "intermediary" transform that I selectively copy to the main transform under certain conditions. works pretty well!
I think its possible the original problem was user error too, it might have been that the jitter I assumed was systems fighting over the animation was actually something else.
Thank you for your time!
I was guessing if maybe the jitter was the terrain is just really rough or something. Anyways, glad your problem is fixed.
Sharing a TextColor interpolator (this is an exact copy of the ones in the crate, just thought it might be useful for other people too. Don't forget to add the plugin).
use bevy_tween::interpolate::Interpolator;
#[derive(Debug, Default, Clone, PartialEq, Reflect)]
pub struct TextColorInterpolator {
pub start: Color,
pub end: Color,
}
pub struct TextColorInterpolatorPlugin;
impl Plugin for TextColorInterpolatorPlugin {
fn build(&self, app: &mut App) {
app.add_tween_systems(component_tween_system::<TextColorInterpolator>());
}
}
impl Interpolator for TextColorInterpolator {
type Item = TextColor;
fn interpolate(&self, item: &mut Self::Item, value: f32) {
item.0 = self.start.mix(&self.end, value)
}
}
pub fn text_color_to(to: Color) -> impl Fn(&mut Color) -> TextColorInterpolator {
move |state| {
let start = *state;
let end = to;
*state = to;
TextColorInterpolator { start, end }
}
}
Hello, I have a hard time understanding what a TimeRunner is and what it does?
and why I need one?
in the examples, we use TimeRunner and TimeSpan, what is the difference, what do both do?
i have this for example:
commands
.entity(entity)
.insert(AnimationTarget)
.with_children(|commands| {
commands
.spawn(TimeRunner::new(Duration::from_secs(1)))
.with_children(|commands| {
commands.spawn((
TimeSpan::try_from(..Duration::from_secs(1)).unwrap(),
EaseKind::QuadraticInOut,
ComponentTween::new_target(
TargetComponent::marker(),
CardOffsetTween {
start: Vec3::ZERO,
end: Vec3::new(100.0, 0.0, 0.0),
},
),
));
});
});
When I change the TimeRunner, nothing seems to change. but when i change the TimeSpan it takes longer to complete the animation.
TimeRunner is what supplying the current time to its children. Setting the time in TimeRunner just means the amount of time for the animation to run for and it will stop supplying time at the end of the specified time. If TimeRunner is configured to loop then that time effects the looping duration. TimeSpan is like a parameter which each tween is at. If a tween has a TimeSpan with range like 4..5 (second 4 to second 5), the tween will not run for 4 seconds and it will only runs for 1 seconds.
so with the TimeRunner i kind of control the duration of the whole animation, and with the TimeSpan i kind of just control the individual parts of it?
makes sense i guess, thanks :)
one more question, more on how to do something.
what i want to do is, tween an entity towards its target position, easy.
however the target position might change before the tween itself was completed.
currently i simply despawn the current tween and insert a new one with the new target location.
as you can see in the gif, this results in quite a choppy movement.
i'd instead like to check if a animation is still running, if it is i just need to change the target location, and if it's done restart the tween with a new target location.
is the idea in theory the right way and how would i achieve this?
I believe that's what the follow example in the crate is basically doing. It replace the existing tween with a new one whenever the cursor moves.
ooh, must have missed that one. i'll check it out, thank you!
any chance we could get a commands.entity(e).animation().try_insert()? or is there a simple equivalent?
i have observers that add tweens on component removal, but the removal might've been a despawn, causing the tween insertion to panic. with my custom components i just use try_insert, but can't here
I believe there’s a PR for thy right now. You could checkout that one right away or add it your self with an extension trait.
I'm trying to have the camera follow my player like in the follow example:
https://github.com/Multirious/bevy_tween/blob/main/examples/demo/follow.rs
Here's what my code looks like:
https://gist.github.com/Rabbival/62303c41f2f7db096dfe4bb5652590ab
for some reason it jitters on the first frame and I don't know why
What happens if you use other type of ease?
The same
(Let me know if there's a specific kind of easing you'd like me to try out)
Linear first I guess. Then Exponential is the curve that's always handy
It has to be one that starts off quickly or the camera would always be way behind the player (since I create a new one every time the player moves)
Sure thing, one moment
Is it jiterring?
yea
I actually can't see it
It's not as visible now but it jitters
Maybe it's some lag?
Yea, I don't think it's a crate problem
I have no idea why it happens though
It's like only the first frame is problematic
Check the animation duration when it's first running
Using this method the animation will start at like 0% to 100% and while the player is moving they'll always be around 50% to 100%
Maybe it looks like jitter because at 0% to 50% the player will move significantly faster
But that problem shouldn't be visible to linear curve
What about system ordering?
It happens before TweenSystemSet::UpdateInterpolationValue
Maybe the camera is rendering in the same schedule as the tweening?
Could be
Don't know if there's anything I can do about that though haha
It's possible to put system ordering on third party
I think it will goes something along like "camera_rendering.after(tween_system)"
Interesting
I'll look it up
Thanks!
hey there - trying to get a number to animate up. anyone see problems with this? it appears to not be doing anything to the Text component when I add an animator
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct TextNumberLens {
pub start: usize,
pub end: usize,
}
impl Lens<Text> for TextNumberLens {
fn lerp(&mut self, target: &mut dyn Targetable<Text>, ratio: f32) {
target.0 = .... .to_string();
}
}
...
Animator::new(Tween::new(
EaseFunction::QuadraticInOut,
Duration::from_secs(1),
TextNumberLens {
start: 0,
end: added_score,
},
))
...
Any ideas on what I'm missing?
ah - missing the system. whoops
Ok so this has taken my entire day
This PR adds the ability to have multiple tweens of the same type affect the same entity at the same time by reporting deltas!
It does add a small breaking change (the signature of interpolate function changes to get the last interpolation value for the delta calculation)
Feel free to check the delta tweens example to see it in practice
Would love to get reviews for it and hear your thoughts!
Now, I need to step up from this chair and go do something else haha
I'm about to release this in a new 0.9.0 version. Would love to hear your thoughts and feedback.
Please feel free to write to me (preferably here but don't forget to ping me) if you encounter any bugs or problems.
hello! i think im having a bug ?
commands.entity(entity).animation().insert_tween_here(
Duration::from_millis(150),
EaseKind::QuadraticOut,
entity.into_target().with_closure(
move |component: &mut UiWorldPosition, _ease_time: f32, progress: f32| {
component.0 = current_pos.lerp(target_pos, progress);
},
),
);
the first call to the closure, progress is already at 1.. the following calls then goes from the start normally. this make the target flicked for one frame to the target, then back to the start
also, is inserting a new animation on a running animation to overwrite the previous one ok ?
in the first tick (closure call) of the new animation, the progress is still the old animation progress. following calls are ok. this is probably the same problem
I did not see that
Please open a github issue or ping me next time
Taking a look now
Should be fine
Taking one additional frame is fine, it depends on your system ordering
But I am curious about it starting at 1.0 by default, taking a look in the repo now
@weary flame are you sure about the parameters of your closure?
I just looked at the crate's code and it looks fine
I assume you were looking at the follow or interpolator examples, but it seems that there the order of the parameters is the current interpolation value as the first f32 (the second parameter)
hey, thanks for answering ! I'll try to do a reproduction when I get back
the animation is added in a Picking observer
oh my god i was using the previous progress... sorry
no problem haha
When I update the crate to 0.17 in the next few days, I'll make sure to make it clearer which f32 is which
hello ! i was wondering:
- how to handle multiples different animation on the same entity ? should just always spawn animation fully in a child entity ? i think when i used .insert() it still conflicted, overwriting previous animation, so i use .insert() but on a new empty child entity.
- bevy_tween_helpers cleanup plugins despawn the entity from what i understand in the readme ? how to simply despawn my child animation entities without removing the AnimationTarget ?
thanks !
I'm on a vacation, could you please remind me on Tuesday?
Oh wait
I got a notification on the channel but then saw that this is actually from a week ago?
Please ping me next time
@weary flame Tuesday, ok?
If you didn't solve that already
hope you had good vacations :)
no problem, i have a few things in my list i had to do before looking into that, so i didn't yet resolve it, but im sure this is no magic, just wondering the best way of doing this.
thanks
The vacation isn't done yet haha
Could you please ping me on Tuesday?
(It's a long vacation so my todo list is packed)
yeah see you on tuesday :) didn't want to ping you now has there's no urgency
Alright, time to look at that
So for the first question- you can use an existing delta tween or implement your own using the provided previous_value in the inteprolate functions. Here's an example that uses delta tweens to, for example, change the translation of an entity by two different tweens at once:
https://github.com/Multirious/bevy_tween/blob/main/examples/demo/delta_tweens.rs
You can see how that's implemented here (if you want to make your own):
https://github.com/Multirious/bevy_tween/blob/main/src/interpolate/transform.rs
I'll be back for the second question in a bit
What do you mean by how to simply despawn my child animation entities without removing the AnimationTarget ??
i meant doing multiples animation().insert(...) on the same entity, that can be triggered at different time by different place, but they animate different values, for example one translation and one scale.
separating entirely the animations works (doing .animation.insert(...) on multiples child entities, so it works now, but i dont know if its normal.
thanks for the resources
since im spawning all my animations in child of the AnimationTarget entity, it create new entities every time. i would like to cleanup the child entities when they have finished animating. ideally keeping the AnimationTarget component on the parent for future animation added
thanks :)
Oh there are actually built-in combinators for that. Have a look here at parallel for example:
https://github.com/Multirious/bevy_tween/blob/main/src/combinator/animation_combinators.rs
You could see it used in practice in the example I attached above
yes, but they are added from different place, for different reasons, i can't add them all at once, some are not always hapening etc.
If you register the bevy_tween_helpers cleanup plugins (for each type of tween you'd like to cleanup), they should despawn automatically once done, and the parent would despawn automatically once all of its tween children are done
You could add different tweens conditionally, but the idea is that when an animation is spawned, all of its child tweens are known. If you'd like to replace some of them with different ones, either track them manually or use TweenPriorityToOthersOfType from the helpers crate:
https://github.com/Rabbival/bevy_tween_helpers/blob/main/src/tween_priority.rs
@weary flame please let me know if that answers your questions
thanks this works, i was confused at the description of these plugins, i thought they would despawn the target entity (the one with AnimaionTarget), but only if it had the animation on it, since im running everything on child its perfect.
ok cool, for now everything on childs works as they all touch differents values
thanks !!
Could you please write a description you think is more accurate?
(English is my second language so I'm having trouble expressing myself clearly at times. Though that happens on my first one as well haha)
Happy to help
sorry im also not english haha. i guess its especially the specific terms, like for example "animation parent" which one is the animation ? as in my case for example there is 3 different entities in the hierarchy. now i know its the one with the timer runner, but that's hard to understand the different possibilities as new comer. i dont how we can improve this
@quartz locust does bevy_tween allow tweening UiTransforms translation and scale?
You can create your own interpolators so basically yea
I've never dealt with UiTransforms but in theory there shouldn't be a problem
Yeah they are pretty new anyway
Yea haha
I'n used to writing ui locations as nodes with percentages, it's cool that they have transforms now
Let me know if you tried to make intrpolators for them and if so how it went
If you think they'd be useful for other people we could add them to the helpers crate
They always had transforms 🧐
It’s just that now they have their custom ones
@quartz locust the problem is that UiTransform translation operates on Val which is hard to interpolate pre-0.18
I'll give it a try and get back to you about that one
So, given the current version of Bevy, the closest thing you can do is something like:
#[derive(Debug, Default, Clone, PartialEq, Reflect)]
pub struct UiTranslationInterpolator {
pub start: Val2,
pub end: Val2,
}
impl Interpolator for UiTranslationInterpolator {
type Item = UiTransform;
fn interpolate(&self, item: &mut Self::Item, value: f32, _previous_value: f32) {
if let Val2 { x: Val::Px(start_x), y: Val::Px(start_y) } = self.start
&& let Val2 { x: Val::Px(end_x), y: Val::Px(end_y) } = self.end
{
let current_x = start_x.lerp(end_x, value);
let current_y = start_y.lerp(end_y, value);
item.translation = Val2 { x: Val::Px(current_x), y: Val::Px(current_y) }
}
//TODO: match other Val variants here
}
}
Made a sprite.custom_size interpolator, putting here in case someone else needs it
@ocean crane I saw your PR, it'll take us some time (I think, about a week and a half) to get to it
Multirious is busy for the next couple of days and there's a new fearture we want to merge before bumping the version (you can actually see an open PR related to it on the time_runner crate)
all good - i've got a locally patched version working for me - thanks for the heads up
I looked at the PRs and both look good, thanks for making them
(still waiting for Multirious to be back though)
Do you want to take a look at my PRs and tell me your thoughts?
I'm basically trying to make Fixed tweens for physics (specifically, in my case, avian) calculations
Avian already has a tween crate, but it doesn't have all the features that one has
nvm I just realized that ticking the timer runners on a special step doesn't matter if you don't also sample them (and the lookup curves) on that step, so I have a few more hours of work to do
Tomorrow, that is
so i'm looking at https://github.com/Multirious/bevy_tween/pull/75
and when i've updated my code to use it with no other changes, none of my tweens run anymore
that .. seems wrong, somehow. i don't know if that means my code has always been broken or there's an issue in the new branch. i haven't dug into this much yet
let mut camera_commands = commands.entity(camera_entity);
let camera_target = camera_commands.id().into_target();
camera_commands.animation().insert_tween_here(
Duration::from_secs_f64(MascotTasks::LOGIC_RATE),
EaseKind::Linear,
camera_target.with(interpolate::translation(
camera_xform.translation,
translation,
)),
);
my tweens are pretty simple
This pr is failing due to mismatch in versions with the curve crate
So the branch is faulty
We're working on a new update for the crate (and a very exciting one imo) that would come with the 0.18 upate
It's here:
https://github.com/Multirious/bevy_tween/pull/78
You can route your game there but we're currently working on it so things might break under your feet
(We are planning on merging it soon though)
looking forward to this!
Been waiting for this for weeks, please
Multirious is busy and I don't want to merge things in his crate without his premission, especially not a change this drastic.
Feel free to route your cargo.toml to this branch.
For those who need to move to 0.18 as soon as possible, you can use this dependency:
bevy_tween = { git = "https://github.com/Multirious/bevy_tween", branch = "generic_time_steps" }
We're not working on that branch, and it has bevy 0.18 and roughly the same api
Is the branch for 0.18 broken? Got this error while pointing at it
364 | use bevy_time_runner::TimeRunnerSystemsPlugin;
| ^^^^^^^^^^^^^^^^^^-----------------------
| | |
| | help: a similar name exists in the module: `TimeRunnerPlugin`
| no `TimeRunnerSystemsPlugin` in the root
error: cannot construct `TimeRunnerPlugin<_>` with struct literal syntax due to private fields
--> /Users/wentao/.cargo/git/checkouts/bevy_tween-d2140e841646a492/2904aee/src/lib.rs:570:29
|
570 | app.add_plugins(bevy_time_runner::TimeRunnerPlugin {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: ...and other private field `marker` that was not provided
help: you might have meant to use the `in_schedule` associated function
|
570 - app.add_plugins(bevy_time_runner::TimeRunnerPlugin {
571 - schedule: self.app_resource.default_schedule,
572 - enable_debug: self.enable_time_runner_debug,
573 - });
570 + app.add_plugins(bevy_time_runner::TimeRunnerPlugin::in_schedule(_));
|
help: consider using the `Default` trait
|
570 - app.add_plugins(bevy_time_runner::TimeRunnerPlugin {
570 + app.add_plugins(<bevy_time_runner::TimeRunnerPlugin as std::default::Default>::default());
|
Yea, sorry, we're still working on the update so time_runner is being changed as well
What branch is this? I'll fix it now
Can you route your game to the main branch?
(it's already updated to 0.18 and seems to build fine)
I tried both main and your branch yesterday. I'll try the main branch again later today
Yes please
It's constantly changing because we're in the process of a big change
Main should be fine now
Alright, running an example on the main branch now
If it succeeds- I'll let you know, If it fails- I'll put whatever fixes necessary on a new branch, and make sure to keep it stable on time_runner crate changes.
Good news: it compiles, so you should probably cargo update
Bad news: things don't seem to move, but I'm on it
Ok done, allow me to clarify
In short: route your repo to the branch called "0.18-patch-until-we-merge-generic-time-contexts"
What was the problem on main: animations didn't run
Why: no TimeContext was provided
What does it mean, what we're doing for so long and why:
- The update we're working on would allow you to run tweens with time contexts (for example,
Fixed) on a schedule of your desire, additional to regular tweens - What that means is that
TimeRunners aka animation parents need a time context to know what time to use (the new exposed api is pretty similar to the old one, it's not 100% done so I don't want to say too much) - Why are we even doing that - imagine wanting to run a Translation tween of a physics object, or a tween that applies velocities etc. Running it with regular time on PostUpdate wouldn't make sense and result in many unstable and hard-to-replicate errors. Well- not anymore (or should I say, soon enough not anymore). You would soon be able to make any tween you make run on different time scales, on whatever schedule intern you desire. But, it does take time to write, and we're both busy with other things.
Please bear with us.
does the bevy_tween is updated to bevy 0.18?
There's no official version yet but you can route your cargo.toml to the branch called "0.18-patch-until-we-merge-generic-time-contexts"
Long name😄
yea haha I was tired and wanted to get the patch in asap so I didn't really think about it
does this correct :
bevy_tween = { git = "https://github.com/Multirious/bevy_tween", branch = "0.18-patch-until-we-merge-generic-time-contexts" }
because it doesn;t work for me
It should be
What error does it give
(Also please ping me if you need me)
I am afraid some guys hate that😀
Feel free, for crate maintainance
What is the error?
As soon I will get home I will send thanks
Thanks please ping me when you do
It's already evening here so I might get to that tomorrow
@quartz locust did you get the error?
Thanks for the ping, taking a look
huh
(checking)
I may have forgot to cargo update 🥲
anyway, this branch will be fixed in the next half an hour or so
@karmic flare can you please do cargo update and try again
I will
but to make the game run I remove all tween functionnality
I need to redo it
@quartz locust it works now
I try to run the crate
Thanks
No problem
Doing out best
v0.12.0 is released! Thanks everyone for being patience. This release took way longer than it should've. There are quite a bit of changes. Please read the changelog carefully.
hey there, Im having a bit of hard time trying to understand TimerRunner. I'm trying to manually trigger the animation again, but doesn't seem to get it to work. The animation only trigger once, which I assume it was trigger during insert. But it doesn't trigger again after even though it does goes through else condition.
...
if !has_runner {
ent_com.animation().insert(sequence((
tween(
Duration::from_secs_f32(0.1),
EaseKind::Linear,
power_supply
.target
.into_target()
.transform_state(*trans)
.scale_to(Vec3::splat(1.5)),
),
tween(
Duration::from_secs_f32(0.5),
EaseKind::ElasticOut,
power_supply
.target
.into_target()
.transform_state(*trans)
.scale_to(Vec3::splat(1.)),
),
)));
} else {
ent_com
.animation()
.repeat(bevy_tween::prelude::Repeat::times(1));
}
...
Any Ideas on what I'm missing?
Maybe try time_runner.set_tick(Duration::ZERO)?
Repeat is not a command passed to the animation, but a modifying constructor
So you basically create there a new animation that repeats once, but it's empty
You should spawn the animation again and kill the previous one if it exists
Might want to take a look at bevy_tween_helpers crate
yup, just what I wanted. Thanks!