-
-
Notifications
You must be signed in to change notification settings - Fork 21.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue with Animation Tree transition logic #98617
Comments
Please upload a minimal project to make testing and fixing this easier, to get all the details to match your setup |
Apologies, I have uploaded a rough example which covers it perfectly. |
It seems that you are simply inexperienced in the way StateMachine is configured. Since StateMachine is nestable, if you want non-Idle animations to transition between each other without going through Idle, you would normally have the following configuration. Recommended1: Also, if you have a large number of Actions, you can have two NestedStateMachines for Actions if you use something like AnyState, and by alternating the transitions between them, you can make transitions between all combinations placed in the StateMachine. Recommended2:
See also NodeStateMachine - State Machine Type. |
Thank you for the information and the article which was really informative and honestly baffled me as it is not present in the docs or I just didn't find it. However none of the solutions in the article or the comment solves the original issue. To be more specific of course:
Let me give an example: This is how our main project which we are currently migrating to Godot looks like in UE5: Each of these States is a state machine that has other nested state machines inside it as needed. Example:
@TokageItLab I am not perfectly acquainted with the Animation Tree so I may be missing something still but if there is a way to achieve the above mentioned functionality please let me know. Thank you in advance! |
As I already mentioned above, you should consider "having only one Transition between Idle and Action". And Action is a nested StateMachine, BlendTree or BlendSpace2D, with mutual transitions other than the Idle animation. Unless you include an Idle animation in it, it will not show up unless an Idle animation occurs in top level Idle <=> Action transitions. Simply put, you should think in the way of not including in the transition path any animations that you do not want to be displayed during the transition. If you are concerned about the number of connections, it is also I say the same thing over again, create two AnyStates in the Action and switch between them, changing the Transition value according to the state. In this configuration, if there are 100 states, you needs only put 100 state + put 2 Any states + connect 2 transitions, not put 100 state + connect 100*100 transitions each other. Or, simply you can create an add-on script for editor to interconnect everything in the nested state machine.
For example, if you have several more SubStates in a nested StateMachine and you want them to interconnect without displaying an Idle, simply connect the SubStates to each other without going through an Idle. However, if you want to “directly” connect things in different SubStateMachines, such as things in SubState A and things in SubState B, which are siblings, that means they should not be SubStated in the first place except AnyState approach. To be more specific, if a transition in StateMachine A is in progress (animations in StateMachine A are blended) and an attempt is made to transition to StateMachine B, the final result blends StateMachine A blended result and the current state of StateMachine B, so there is double blending. Grouped mode solves this to some extent, but it does not support multiple ports of input/output #88878, so the currently recommended solution is to use AnyState with Nested mode. |
I understand, I did some research on the topic of AnyStates as well as asked a friend that works in Unity as it seems that the notion comes from there and there is practically zero documentation about it from the Godot side of things. I now understand the principle better and think that this may be what I need in animating it but I still struggle to understand from your photo how exactly it works in Godot specifically as in Unity you simply drop the AnyState transition with a condition and it works but here I do not have such a node and your photo shows zero transitions to the states inside the state machine substates. Before I ask my question I'd like to explain my original idea as it may have been missed. This approach made it very easy to make transitions into the different Main state machines without making transitions from each one to another and backwards. I have also had the idea to create the following. I have a Root state machine which has a start directly connected to another state machine which has all of my actions inside of it. Then I make a duplicate like in your example and connect the two duplicate state machines with transitions AtEnd style so that I do not break the loops inside since they will always transition into one another and there is no condition for the transitions. Inside the State Machine duplicates I have transitions from the start node to each state with a condition and transitions from each state to End with a condition as well. This way I can go from one state to the other without relying on Idle but the issue is that each time I change states my character goes through his Reset pose and it is visible for atleast one frame where the char is in TPose I suspect because we go to End in the state machine but if I do not use End then I have no way to stop the current animation as I cannot make a transition from a state to Start(makes sense). I will upload the modified project for reference. @TokageItLab Of course if there is also another way or if there is a way to make my idea work without the TPose being visible between animations and I can blend them with the XFade then perfect. I do appreciate the feedback and the suggestions! |
The article in NodeStateMachine - State Machine Type is the most detailed so far on the use of AnyState, and it is also part of the documentation because it is linked from the documentation. Also, the original design concept is described in #75759. In some cases, StateMachine cannot detect the end time depending on its connection and state. Root mode and Nested mode differ in how they detect end time and how they behave when seeking. Root
Nested
Note that a Nested mode StateMachine can have a connection with its child states, but if the state is not a dead end and has a connection, it will not detect the end.
This is expected since there is no longer an Idle state above it and there are no animations available for playback, hence the bone rest is displayed. Reconsider the settings of the connection to the Idle state at the top level. Or it could be due to a one frame playback delay. In that case, check to see if PR #94372 helps; a workaround available before #94372 is merged would be to set a minimum crossfade, such as
You can also consider using NodeTransition in the nested BlendTree like: Root:
Action(BlendTree):
|
Honestly I tried everything I can. I tried the advice with the XFade time and it did not work no matter where I placed it. I even tried to compile your branch from #94372 and set the advance flag on every single animation and it not improve. As far as I saw from logging the behaviour when I go from one state to the End node and transitioning via AtEnd transition without any condition there are two prints(ticks) where both state machines sit at the End state. This is the reason the character goes into a TPose because the animation tree has not yet transitioned into the new state. If this happened instantly(in the same tick) then the TPose animation would not be shown. I am aware now of the advance(0) function as I mainly write my game logic in C++ but I have no idea how to force that during a transition. @TokageItLab |
Above is just recommended structure of statemachine. So finally, recommended code for AnyState is below. Godot-4.3-Third-Person-Controller-any-state.zip @onready var animator : AnimationTree = $AnimationTree
@onready var animator_state : AnimationNodeStateMachinePlayback = animator.get("parameters/SM/Action/playback")
@onready var animator_state_action : Array[AnimationNodeStateMachinePlayback] = [animator.get("parameters/SM/Action/StateMachine/playback"), animator.get("parameters/SM/Action/StateMachine 2/playback")]
@onready var animator_state_action_names: Array[String] = ["StateMachine", "StateMachine 2"]
@onready var animator_state_action_idx : int = 0
@onready var animator_state_current_action : String = ""
func animate(delta):
var prev_action : String = animator_state_current_action
animator_state_current_action = ""
if is_on_floor():
if velocity.length() > 0:
if speed == run_speed:
animator.set("parameters/SM/conditions/idle", false)
animator.idle = false
animator_state_current_action = "Run"
else:
animator.set("parameters/SM/conditions/idle", false)
animator.idle = false
animator_state_current_action = "Walk"
else:
animator.set("parameters/SM/conditions/idle", true)
animator.idle = true
else:
animator.set("parameters/SM/conditions/idle", false)
animator.idle = false
animator_state_current_action = "Air"
if prev_action != animator_state_current_action && !animator_state_current_action.is_empty():
animator_state_action[animator_state_action_idx].start(animator_state_current_action)
animator_state.travel(animator_state_action_names[animator_state_action_idx])
animator_state_action_idx = (animator_state_action_idx + 1) % 2 |
Firstly, thanks once again for the ideas. I was wondering how AnyState worked in here but this example shown is not going to work for me. I have 80 animations and will not be writing code for every single one of them. This example with the 4 animations works but is not my exact use case. The goal is to achieve this through the Animation Tree entirely while outside we only set an enum with the current anim state. The fixed version has a nice transition from Idle to the Action but when changing states between Actions inside it still goes into TPose during the transition.
One way is to call advance(0) when detecting that we are at End in both machines to force the transition to happen this tick.
Is there an easy way to modify the engine code to implement this? |
The problem is that if there is a transition in the AnyState, the seek may not work. As explained above, AnyState restarts the current State when it restarts, so transitions to End and Start can cause delays and other problems since the current State is lost. The latter AnyStateExample sent in #98617 (comment) has a different configuration of Fixed and StateMachine on it (no internal connection), but it should work.
I am not sure what you mean by this. In your code in the past, jump and air have been bool parameterized, so I don't know what the difference is. The code I have suggested with AnyState makes them into strings, which can be cached as arrays by accessing the StateMachine resource at Ready timing, etc., so it should be possible to automate this to some extent.
Grouped mode solves this problem to some extent, but as explained in #88878, it is currently unsafe because it does not have multiple in/out ports. |
I understand, I think the anystate even with the automation mentioned would probably be too much work considering I plan on having multiple nested machines so it would probably turn out to be less ideal for the use case. The MRP was created to showcase the issue while in my actual project I have not used a bool parameter to switch animations and only use the advanced expression to check for enum values or a combination of conditions. With that in mind the classic transition creation for each state to another will have to do. Last question: Would it be too difficult to implement engine logic that works like so:
The goal being doing things entirely in the animation tree while achieving the original idea of transitioning through multiple states without triggering fade events in between transitions unless told to by a flagon the transition or something like it. |
I don't think there is any difference in labor yet. The only difference is whether the final condition is written in the Transition of the AnimationTree or in the script, and the amount of condition writing has not changed.
Do you mean that you want to create a path by travel and then transition to the last animation ignoring the middle of it? Technically, it would be possible, but in that case, we would have to create a secure API. For example, if you have a path like:
If you want to do a transition from A to D without B-C, you will not know which xfade or other parameter to refer to for the transition. So in that case, I think the proposal/PR would be to implement an xfade parameter for teleportation. This way, after generating the travel path, the user can teleport at optional by retrieving only the end of the path and discarding the travel path. |
I apologize for the month late reply. Your assessment is correct. This would work as a teleport and my idea is to have it possibly be toggle-able for specific states or transitions inside a state machine so that it can trigger automatically but be controllable with the flags. You are also correct that the xfade to be used in that case is a bit ambiguous. I did make a check to see how this works in UnrealEngine as there it is already implemented. In UE it works like so: I am not saying it is perfect but it does work. |
Tested versions
System information
Godot v4.3.stable - Windows 10.0.26100 - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 4080 SUPER (NVIDIA; 32.0.15.6603) - Intel(R) Core(TM) i9-14900K (32 Threads)
Issue description
Me and my team are facing an issue with using a more advanced animation tree with nested state machines and animation blending through fade.
The issue is quite simple actually. When using an Idle state as a conduit to all other states and trying to transition from lets say Crouch state into the Sprinting state we go through the Idle state. When animation xFade time is applied the result is that we fade towards the Idle and then we fade from Idle towards the Sprint state.
Usually what we would expect is for the Animation tree to resolve the final state and fade from Crouch to Sprint directly instead of having to make a separate transition between the two states.
Without this we are forced to make transitions considering every possible combination from our numerous states towards each other and not only does this become harder to maintain and prone to edge cases(at least a lot more than usual) it also makes our animation tree look like a (sorry but couldn't picture it better) spaghetti dish making it very hard to understand what goes where. Another contributing factor is the UI not giving options to have reroute nodes to better place the transition nodes so they look more readable.
For example Unreal does this by default and works perfectly for our case but we decided to switch to Godot in our belief that it is the better engine for its modularity and open-source approach. If we can have this at least as an option somewhere it would be amazing.
We also cannot find a way to have a blend weight to an animation track so that when blending/fading animations for example the animation method call track doesn't trigger on the animation that is being faded out during the blend. But this could be us not finding the proper option in all of these cases so please feel free to correct me.
Thank you in advance and apologies for taking your time!
Steps to reproduce
Create a Project.
Add a 3D character with animations and animation tree.
Make the animation tree root node a state machine.
Create an Idle state.
Create 2 more states that transition from and to Idle state but not to each other. State 1 and State 2
Set xFade time on all transitions to 0.2 for example. Higher would exaggerate the issue.
Make advanced transition rules.
Create a situation which requires State 1 to transition to State 2.
Minimal reproduction project (MRP)
Godot-4.3-Third-Person-Controller.zip
The text was updated successfully, but these errors were encountered: