godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.13k stars 78 forks source link

Add support for relative position/rotation/scale keys for AnimationPlayer keyframes #1191

Open WinterVein opened 4 years ago

WinterVein commented 4 years ago

Describe the project you are working on: A 2D platformer about a guy who works in a mine

Describe the problem or limitation you are having in your project:

I need my character's position to change along with an animation

Describe the feature / enhancement and how it helps to overcome the problem or limitation:

in the inspector window, when I try to add a keyframe for the position (transform) of the parent node (which is the player), when that animation plays, my character is teleported to that transform position

Describe how your proposal will work, with code, pseudocode, mockups, and/or diagrams:

imagine this is an AnimationPlayer timeline for an animation named "move character" ---(keyframe one, Transform (x=0, y=0) )----(keyframe two, Transform( x= 30, y=40))----

now in my code for the parent node (my player) i would write $AnimationPlayer.play("move character")

moves the character 30 pixels to the right and 40 pixels down from their position before the animation was played instead of teleporting the player to the coordinates of x = 30, y = 40 from the starting position of the scene.

If this enhancement will not be used often, can it be worked around with a few lines of script?:

Not really. There are workarounds that involve tweening but it's more considerably more complicated as you cannot easily sync the Character's animation to their movement on the plane, like you would be able to if this suggestion is implimented.

Is there a reason why this should be core and not an add-on in the asset library?:

Yes, because It really seems like it should be a pretty basic and standard feature given that the need to move to a specific set of static and unchanging pre-specified coordinates on the screen seems like it has very limited use compared to be able to move of coordinates relative to your character's starting position while undergoing an animation.

Calinou commented 4 years ago

You can use a parent-child node structure to effectively get relative transforms.

KoBeWi commented 4 years ago

You can use a parent-child node structure to effectively get relative transforms.

Combine it with a method call that updates position when animation loops and you can move objects with AnimationPlayer. Here's an example: ExampleProject.zip

It might be tricky in some cases, but it's definitely possible to do.

golddotasksquestions commented 1 year ago

@Calinou

You can use a parent-child node structure to effectively get relative transforms.

In a single specific and unique situation, this might be true. However if you want to project wide add relative animation at any time, adding a specific parent node to all your scenes and nodes which might need relative animation, is just not really feasible.

One of the many usecases I imagine for this proposed feature would be cutscenes, for instance. Character scenes often have a physics body with importants code as root node for a good reason. Now adding some other parent totally fucks up the whole scene and code structure. Just think about the interplay between moving characters and Area nodes for example.

The much cleaner solution would be support of relative animation as proposed here. Animation keys that add their value to existing property values, rather than replace it.

Vognee commented 9 months ago

Wasted a couple of days just trying to figure out why animation working just fine in its own scene defaulting to 0,0 when instanced in a parent scene. I’m new to Godot, but it baffles me to no end, that this simple feature is still not implemented, the problem is from 2016, how is it not fixed yet? The solution is quite simple - if you instance animation to a parent scene all its coordinates should be relative if you did’t uncheck a checkbox, like “local coordinates” or something in Animation player.

It’s simple and logical - why would I want to play my animation at default coordinates when I deliberately placed animated object not on those coordinates, man I’m frustrated.

Calinou commented 9 months ago

The solution is quite simple - if you instance animation to a parent scene all its coordinates should be relative if you did’t uncheck a checkbox, like “local coordinates” or something in Animation player.

Relative keyframes have a lot of caveats that absolute keyframes don't. For instance, you may want only some of the keyframes to be relative, or only some of the tracks. There is a lot of complexity that isn't obvious at first, but will become apparent when someone tries to implement this.

The animation code is becoming very complex lately (example), so I wouldn't hold my breath for this being implemented soon.

Vognee commented 9 months ago

The solution is quite simple - if you instance animation to a parent scene all its coordinates should be relative if you did’t uncheck a checkbox, like “local coordinates” or something in Animation player.

Relative keyframes have a lot of caveats that absolute keyframes don't. For instance, you may want only some of the keyframes to be relative, or only some of the tracks. There is a lot of complexity that isn't obvious at first, but will become apparent when someone tries to implement this.

The animation code is becoming very complex lately (example), so I wouldn't hold my breath for this being implemented soon.

If it wasn’t resolved from 2016 it seems it is complex, alright. But at least some experimental function where it should work logically if you checked the right box could be implemented I feel, because it ruins Godot for me: it becomes a nightmare to fiddle with every scene local/global coordinates and each time second guess yourself - is it not working because you did something wrong or the engine is broken.

TokageItLab commented 9 months ago

the problem is from 2016, how is it not fixed yet?

In 3DTransform, there is already a method to extract relative values as root motion, and it is often used. However, there is only one root motion per AnimationMixer, and we need to add the relative values in gdscript. If we could make this work for other tracks as well, it would theoretically be possible to extract relative values, but there are two necessary implementations.

  1. we need to take into account fading, blending, etc., so we must add an accumulator to the track cache and add a delta calculator to the blending processor to extract relative values. Since the animation applies a value every frame, it is necessary to calculate delta. For example, if you simply add the animation values to the original object, a linear transition of 1, 2, 3, 4 becomes an exponential like transition as +1,+3,+6,+10. Converting between absolute and relative values in animation is probably more complicated than you think. See also https://github.com/godotengine/godot/pull/72931.

  2. The root motion is associated with one per AnimationMixer, and you still need to add the relative values yourself in gdscript. This should not be done on every track, so we need to design a GUI. Ideally, there would be a GUI that would encompass all the tracks that AnimationMixer has, but this is actually not in Godot. Currently, the RESET track is used instead, but it is quite a Hack. Also the AnimationTrackEditor code has been unmaintained (mostly hard coded as a stopgap) for quite some time, so this may require some very extensive work.

Well, as long as the blend does not exist, 1 in the above can be implemented in a user-side script. Just animate the value with a dummy prop/track, calculate the delta and apply it to any property.

golddotasksquestions commented 9 months ago

@TokageItLab

For example, if you simply add the animation values to the original object, a linear transition of 1, 2, 3, 4 becomes an exponential like transition as +1,+3,+6,+10.

This exponential behavior could be exactly what is desired with relative animations.

The relative animation key value is probably supposed to be added to the current value, regardless what the current value is. That's what differentiates it from the absolute values the AnimationPlayer currently sets.

If the user does not want to add to that value (or subtract from it using negative values), they can simply key in a 0.

The AnimationPlayer would only blend the keyed values on the relative track, not the properties those values are then added to.

TokageItLab commented 9 months ago

@golddotasksquestions Probably you do not understand the problem correctly.

If you play the relative animation to this case, what should the final value ideally be?

If you do not calculate delta, the animated values will be applied as follows:

10 + ( 0 + 2 + 4 + 6 + 8 + 10 ) = 40

The above should not be the result most people are looking for in relative animation. So you need to calculate delta as follows:

10 + { 0 + (2 - 0) + (4 - 2) + (6 - 4) + (8 - 6) + (10 - 8) } = 20

If there are no blends or crossfades, this can be calculated in gdscript, but if not so, more complex calculations are required as I commented above.

golddotasksquestions commented 9 months ago

@TokageItLab

Thank you for double checking, but I have understood. After I understood (thanks to your previous comment), I actually went back to this comment and crossed out my previous proposal, because I agree that's adding a lot of complexity.

10 + ( 0 + 2 + 4 + 6 + 8 + 10 ) = 40

I would go with this route.

Because it's simple and easy to maintain and understand and to communicate.

If the user does not want to end up at 40, they would have to use discrete blending (aka no blending), or add a key with value 0 in the frame before/after they have a key with value 10.

Honestly the more I think about it, the more I would prefer this behavior.

TokageItLab commented 9 months ago

Because it's simple and easy to maintain and understand and to communicate.

This would be doubtful. The problem there in particular is that the final value is changed depends on the Animation FPS, especially the number of frames is unpredictable unless you are in physics mode.

golddotasksquestions commented 9 months ago

especially the number of frames is unpredictable unless you are in physics mode..

So only allow it for physics mode and only update it on physics ticks?

TokageItLab commented 9 months ago

As long as delta is used, the final value is consistent independent of FPS. So, it should use delta.

If there is a relative animation that changes from 0 to 10, applying it to an object with an initial value of 100 will always result in a final value of 110, and applying it to an object with an initial value of 200 will always result in a final value of 210, as long as there is no non-animation input to the object.

If there are non-animation inputs, they will also be taken into account for consistent results. If the above relative animation is applied to an object with an initial value of 300, and the controller input once moves the object by only 5 during animation playback, the final value will be 315.

It is definitely confusing / un-user-friendly when the relative animation values are linear transitions to begin with, but the actual applied values are exponential changes; How do you plan to create the keys in that case? So it will never be implemented as you said; The relative animation you have in mind and that of others are probably far apart.

If you want an exponential change, simply create an animation using the baking easing option in AnimationTrackEditor, or setting a curve to the ValueTrackKey, or using a Bezier Track, and apply it as a relative animation with delta method.

Then, if you really want a value like your method, simply create a user-side accumulator that accumulates the delta of the relative animation every frame, separate from the internal accumulator for blending, but it may be simpler to use value_track_interpolate().

golddotasksquestions commented 9 months ago

If you want an exponential change, simply create an animation using the baking easing option in AnimationTrackEditor, or setting a curve to the ValueTrackKey, or using a Bezier Track, and apply it as a relative animation with delta method.

Then, if you really want a value like your method, simply create a user-side accumulator that accumulates the delta of the relative animation every frame, separate from the internal accumulator for blending, but it may be simpler to use value_track_interpolate().

I don't understand how this is supposed to work and how I would have to implement it. Nothing about it sounds "simple" to me.

TokageItLab commented 9 months ago

You must learn at least BASIC animation feature: how to use easing on AnimationPlayer (one of the methods of easing is written in the tutorial https://docs.godotengine.org/en/stable/tutorials/animation/introduction.html#edit-keyframes). Then you need to learn about root motion and get to know the actual relative animation use cases there; Relative animation should be an extended implementation from root motion.

golddotasksquestions commented 9 months ago

Relative animation should be an extended implementation from root motion.

I seen more users asking about it for 2D projects (and I don't mean skeletal animation). I'm only familiar with root motion in a 3D context.

TokageItLab commented 9 months ago

Probably you do not understand correctly what people are asking about.

Most of users understands that root motion is currently only available in 3D. What is being demanded is a 2D version of it, and I am saying that it is almost synonymous with relative animation.

While 2D root motion has such proposal (https://github.com/godotengine/godot-proposals/issues/5303) and PR (https://github.com/godotengine/godot/pull/65183), and relative animation is definitely a more flexible version of that. Relative animation is better than hard-coded 2D root motion which is only available for some properties. In other words, this proposal should be able to supersede https://github.com/godotengine/godot-proposals/issues/5303.

DDarby-Lewis commented 8 months ago

Would it be possible for this to be a more general proposal - the option to set any track in an animation to be an "offset" track which makes it behave additively with whatever the starting value is going into the animation? It could be a dropdown flag alongside the update mode and interpolation mode options. This would enable a variety of complex behaviours to be played out in the animation player which are otherwise need to be implemented in very custom code since the animation player already has the amazing ability to set animations for virtually any property. In my case, it could make complex spell effects dependent on earlier stages, since I am using animations to control VFX/SFX and damage for spells. It can't use relative changes however my options become using an animation tree (overkill and not very efficient) or making it all in code (losing the wonderful animation key frame interface and playback options).

SeanRamey commented 8 months ago

I would like to reference my discussion here: #9005

SeanRamey commented 8 months ago

You can use a parent-child node structure to effectively get relative transforms.

Combine it with a method call that updates position when animation loops and you can move objects with AnimationPlayer. Here's an example: ExampleProject.zip

It might be tricky in some cases, but it's definitely possible to do.

I would like to note that while this technically can work to get relative movement with AnimationPlayer, it does not solve the problem of not being able to watch the overall animation play in the editor, which means you might need to sit through the entire animation in-game just to find out something is still slightly wrong at the very end, and it becomes trial and error.

SeanRamey commented 7 months ago

I'm making an attempt at implementing this feature, but I'm having a hard time because I've never even looked at the engine code before. AnimationMixer and AnimationPlayer are particularly hurting my brain trying to understand. I would love some suggestions and/or some guidance on this. I'm on chat.godotengine.org as sramey40 and also the official Discord server as rameynoodles.

MotasemZakarneh commented 1 month ago

You can use a parent-child node structure to effectively get relative transforms.

Combine it with a method call that updates position when animation loops and you can move objects with AnimationPlayer. Here's an example: ExampleProject.zip It might be tricky in some cases, but it's definitely possible to do.

I would like to note that while this technically can work to get relative movement with AnimationPlayer, it does not solve the problem of not being able to watch the overall animation play in the editor, which means you might need to sit through the entire animation in-game just to find out something is still slightly wrong at the very end, and it becomes trial and error.

i would also like to add, that i am working on a game that used that approach (its a unity project). and essentially at some point, that project some complicated requirements, that forced us to use this style (its a 2D game). we pushed through with this method and all, and the end result was simply subpar.

While it fixed the animation issue, and worked perfectly.

but it opened a doorway for so many other issues, and it turned out its not even worth it.

  1. Rotation, what if you need to flip the player, while the player is playing an animation with that style?
  2. Scaling, if you add scaling into the equation, then everything changes too.
  3. Animations being interuppted (this one is probably the easiest to fix).
  4. Collisions, and boy oh boy, was that a tough beast, this solution will mess up all kinds of collisions.
  5. Pixel perfectness/pixel perfect movement.
  6. Spawning different things like VFXes/particles on appropriate positions
  7. Multiple characters using this weirdly animation based movement at same time.

i can go into detail with each one, and while you can fix each one of these with code, that that will easily turn from a 10 lines of code function, to up to a 1500 lines of code to write it. (Note, Unity's root motion works, but we couldn't use it due to a different topic but thats a different story).

Now, i went brute forced my way through it, and it worked, but what i am trying to say, this solution is Not feasible, its feasible to fix this issue in isolation, but it not feasible at all for an actual finished product/game.


Essentially for anyone that is making an animation heavy 2D game. root motion in 2D is a must have, #5303 we already have root motion in 3D, its a shame we do not have root motion in 2D.

obviously, Relative key frames for the animation player is the optimal solution, but honestly its probably very complicated and would take ages to implement, and quite frankly, for most use cases #5303 root motion, root rotation, root scale for 2D is probably mor than enough to cover most cases.

as we can see in the title of this ticket itself which is "position/rotation/scale" keys. because while doing it for all kinds of keys is great and a cool feature to have, but i wouldn't classify it as essential, because it wouldn't be game making blocking.

However, lack of 2D root motion is very much one of the things that is blocking our time from going to godot, for example.