Open shanayyy opened 2 months ago
The animation control is a rather tricky part of the library. I added it as an experimental feature a while ago but animation control can become very complex and this implementation isn't going to work for all cases.
In many cases (I would even go so far as to say in most cases) the animation length correlates with some game mechanic and has direct influence on how the game feels. E.g. firing animation of a gun needs to be properly aligned to the gun's firing rate, the strike animation of a sword strike needs to correlate with the intended fighting speed of the player or an invulnerability or stun animation is only as long as the designer said these effects would last.
In these cases I think it is not good to synchronize the animation state back to the state chart because the game design will dictate how long these animations play. So the state chart would not "wait" until the animation is finished - but rather control the timing itself. This way the animations are only visualizations of the game state to the player but have no impact on gameplay. For this the AnimationPlayerState works fine, because when you work like this, animations are basically fire and forget. So in your concrete example, say you have a series of attacks that should play in sequence you can do a ...
... and just need to make sure the animation lengths match the designed timings in your state chart.
Then no manual signal mapping is needed. And for this example I think this is the better way to do it as you can tweak the timings until everything feels right and when you're done you just change the animation one time to have the proper length rather than having to modify the animation every time you want to tweak the attack timing.
However there are also cases when you want callbacks from your animation (either through a call track or just by listening to the signals of the animation player / animation tree). This is mainly the case for purely visual things (e.g. UI animations) but there are also some use cases for quick time events. However these are usually very specific to the game and I'm having a really hard time coming up with a solution that is universally useful rather than catering only for a specific use case, like the suggested solution with the Next State flag. Also I really don't want to mix transitions with states which this implementation would do. If you want to keep this approach, I'd suggest you do it this way:
anim_name
, e.g.func on_animation_player_animation_finished(anim_name:StringName):
_state_chart.send_event("animation_finished_" + anim_name)
That's 2 lines of code to get the specific behaviour you want and you can just use the built-in transitions to do the rest of the work. Would that be a workable solution for you?
Thank you for taking the time to write this, it's easy to understand and i agree with you. In fact I'm using your solution.It works really well and doesn't require much boilerplate code, as I've found that I only really need to create one animation_finished event for all animations.
I have tried all the state machine plugins in godot assetlib, and finally found that only this plugin really solved my problem, but I found that ExpressionGuard seemed too slow, so that Auto Transition sometimes missed the input check, so I now send input events every frame instead. This makes the condition state pattern less useful.
I'm not sure what you mean by "expression guard is too slow". A guard never triggers a transition it can only prevent a transition from happening. There are only two ways to trigger a transition:
Could you elaborate on your use case? Maybe there is a nice solution for that that doesn't require sending events every frame.
Haha, that's a typo. I mean "too slow". I drew a diagram to show what I want to do:
I don't think this setup will work because the resolver doesn't actually wait to resolve. When the animations finish the resolver state is entered and the resolver state will immediately walk through all the transitions. If you haven't pressed the key yet, it will not wait but just straight evaluate the other guards and pick the first one matching. One example (TI = Time index in milliseconds):
Or if you press the key slightly before the animation ends:
So this is not caused by the execution speed of the evaluations, they are always fully evaluated within a single frame.
What you describe is exactly how I would expect it to work. There are two places where I need to explain a little more.
TI=50 - attack is pressed, nothing happens anymore because we are already in idle.
If we are in idle, pressing attack will send Input Changed event, so we will jump to Resolver immediately.
they are always fully evaluated within a single frame.
I'm not sure about this, because I found that the delay of a single evaluation can exceed 6ms, so when there are multiple evaluations to be performed it may take more than 1 frame, but it may be that I'm doing it wrong. I will make a demo project later when I have time to see if I can reproduce this.
A common use case for
AnimationPlayerState
is transitioning to a different state at the end of an animation, e.g. returning to the idle state after an attack animation. Currently we have to handle this transition manually. This requires tracking the completion states of different animations and mapping them to transitions.To make
AnimationPlayerState
easier to use, I suggest adding aNext State
property to it. If a state is assigned to it, when theAnimationPlayer
emits theanimation_finished
signal, if theanim_name
is equal to theanimation_name
of theAnimationPlayerState
, and theAnimationPlayerState
is active, it automatically transitions to the state specified by theNext State
.