godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.08k stars 69 forks source link

Add `AnimationPlayer.play_flipped()` to play mirrored 3D animations #8478

Open TheNetherPug opened 8 months ago

TheNetherPug commented 8 months ago

Describe the project you are working on

I am currently working on a 3D, first person video game in Godot 4.2 which contains a full body mesh. In my game, there are multiple instances in which I have complex animations that need to be flipped (mirrored) depending on certain situations.

Describe the problem or limitation you are having in your project

I need the ability to flip complex animations with ease in-engine in order for the game to look, and feel much smoother and polished in a reasonable timeframe. It would take much longer to manually copy and paste the flipped animations in Blender, and reimport them than having a simple function. This would also reduce the size of the exported glb file.

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

A large portion of developers may need to flip complex animations (such as motion captured animations with many keyframes) to fit different situations, but may not have the time to do so manually.

Having two animations, one mirrored and unmirrored causes issues long term, as modifying one animation will require the second flipped animation to be modified as well. Having a simple and easy function or way to easily mirror 3D animations with rigs will be greatly beneficial, as well as in some cases reducing the size of files.

For example, a wall running animation with one arm extended outwards, would need to be flipped depending on which wall the player is on. Another example is mirroring idle animations to match a walk or run animation to ensure feet placement are correct and smoothly transitioned to without complex interpolation logic.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

A simple function.

@onready var anim = $AnimationPlayer

if flipped:
  anim.play_flipped("WALLRUNNING")
else:
  anim.play("WALLRUNNING")

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

I am not aware of any way this can be worked around using a few lines of script.

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

I believe this should be a core addition and not an add-on in the asset library as there are other animation-based nodes that allow mirroring of animations as well as greatly improving Godot's animation feature set from out of the box.

AThousandShips commented 8 months ago

See also:

Calinou commented 8 months ago

A full-body mesh likely requires the use of AnimationTree for animation blending, so having this in AnimationPlayer alone wouldn't cut it.

TheNetherPug commented 8 months ago

A full-body mesh likely requires the use of AnimationTree for animation blending, so having this in AnimationPlayer alone wouldn't cut it.

In my game, I chose to use an AnimationPlayer instead of an AnimationTree as it was simpler to use. Having this functionality in both AnimationTree and AnimationPlayer would be excellent as well, though i'm not too certain about AnimationTree.

Remi123 commented 6 months ago

Just adding my inputs as I've solved this issue in my code, so here is some info on how to do this.

By flipping we mostly mean inverse the location and rotation of the right and left side of the skeleton, usually on the X axis. So rotation from right side are applied to left side, and vice versa.

What I did was inspect each track of the animation and basically modify the key value depending of its type. If it a position track, I multiply the value by Vector3(-1,0,0) If it's a rotation, I multiply the Y and Z component of the Quaternion by -1. This mirror a quaternion on the X axis.

The only thing left is switching the track NodePath, since we mirrored the bone orientation but it's still applied to the incorrect bone. And this is where we need a mapping for each bone to let it define it's mirror counterpart. I have a simple Right/Left nomenclature so it's easy in my workflow, but to be general I suggest the class SkeletonProfile to add a variable for each bone to let it define it's mirror bone, and empty if no mirror like the head.

Once the mapping is done, it's trivial to know which bone mirror which and create mirror animation in editor and at runtime.

If I want to create a new animation, I just use this mapping to swap the node path, and mirror the bone position and rotation. At runtime, the AnimationMixer ( the class that AnimationTree and AnimationPlayer inherit since 4.2 ) could blend using a SkeletonProfile with the mirror info and use it if required.

This logic is basically what Unreal and Unity does.

Hope it help

Lunatix89 commented 4 months ago

I've did some research on this and came out with this solution: https://github.com/godotengine/godot/pull/89070 However, I found out that it's not only about mirroring positions, rotations and bone names, it also needs to have a proper rest pose on the animations to work.

The PR also features the bone counterpart mapping which @Remi123 mentioned. It's in draft mode and meant to be used for discussing this one further.

I also have a separate repo where I have the implementation in a standalone version so it would also be possible to use this in custom builds for users who really need it.

https://github.com/Lunatix89/godot-mirror-animations

TokageItLab commented 4 months ago

The demand to do this without the use of DCC software such as Blender is understandable. However, to make the workflow completely independent of any DCC software, it would require a large amount of functionality, so I think it would be better to make it an add-on.

For example, we could consider a function like RootMotionExtructor that transfers the movement of hips to the root bone, or a function that separates the upper and lower body, but we may not be able to include everything in the importer/editor. We can come up with so many useful functions for editing these kinds of humanoid animations, so it is not realistic to include them all in the core and continue to maintain them.

Especially for editing functions that depend on Humanoid-focused workflows, it is not so generic and depends much on how users handle their data. It is possible that there are many people who do not need it, which could result in providing unstable functionality for a small amount of demand.

If there is a feature missing to create an add-on, it makes sense to implement an API for it. We know that AnimationTrackEditor is not extensible, so we may need to start working on those improvements.