Closed Phanterm closed 10 months ago
Hi Phanterm!
The _process outside FSM works is because it fires multiple times when you pressed the key. derkork's statechart _input() and Godot's input() will fire only once on the press, thus it's very likely that when you use "_animations.travel("Roll")" in the roll state_entered of the state chart, the animation tree takes some frames to transit to "roll" so when you immediately check in roll state if "roll" is playing it returns "not playing".
I don't think checking animation if _animations.get_current_node() != "Roll":
is a good idea to see if it finishes.
You may try connecting the animation_finished of your roll animation state in your logic handling script, and when roll finished, tell the statechart to go back to idle.
or have you tried derkork's Animation tree states?
I don't think checking animation
if _animations.get_current_node() != "Roll":
is a good idea to see if it finishes. You may try connecting the animation_finished of your roll animation state in your logic handling script, and when roll finished, tell the statechart to go back to idle.
Ahh I was afraid of that. Okay, it's easy enough to transition with an animation property function call that fires send_event()
. Thanks!
or have you tried derkork's Animation tree states?
I started out with those, actually! derkork was the one who mentioned to me that it was more trouble than it was worth to use them after I had run into a few issues.😅
Okay, I confirmed that using if _animations.get_current_node() != "Roll":
was definitely not the way to go. And everything that wasn't working was indeed using that method for polling for transitions. Thanks for your help!
I personally tend to think of animations as visual representations of the internal game state for the most part. That is to say, that control only ever flows into one direction - from the internal game state to the animation node.
E.g. in a fighting game the exact timings of each move have to be carefully designed to make the input feel nice and allow for precise timing. And the animations really are only secondary expressions of these timings. Which means that the game code will never wait on any animation to finish.
The game code controls the timings and will just play animations on certain events to give visual feedback to the player. And if you do things this way, then you really don't need any special magic built into the state machine. You just fire animations when needed, or even just use a blend tree to submit data to the animation nodes and the animation state machine does its own thing. No need to wait for any animation_finished event.
I think this approach is a lot less headache as you don't need a constant ping-pong and synchronization between your input and your animation. Plus you don't need to change your animation timings to correct input timings that feel off. You fire the animation, after 0.3 seconds you move out of the roll state using a timed transition and if the animation lenght differs from 0.3 well your visual feedback may be a bit off but that is something you can fix later once you dialed in all the timings so the game is fun to play. Until then the animations are just placeholders anyways.
What are your thoughts about this? Is there something I am missing/overlooking?
I think mainly I am very gun shy from having used Unity's animation tools for so many years and have been consistently let down by its inability to properly time events and property changes specifically on the first and last frames of an animation. However, the more I use Godot's tools, the more I realize that they Just Work™️ without needing do much thinking about it.
Here is how I now have the rolling set up. I've been using more and more function call tracks to fire send_event
. They work consistently.
The only reason why I wanted to initially avoid this is, of course, that changing the animation in any way also means having to reposition the appropriate calls (which is why I wanted to poll the animation state machine's current node instead). But if I were to rely only on a delay timer in-code, it would be the same issue if I adjusted an animation and had to reposition appropriate keyframes; the difference is that I'd be making the adjustment from a different place.
The problem with relying on the code (something like a delayed transition) is that it requires a lot of guess and check to get the timing just right. So if I hang on the end of an animation, it's nice for me to be able to control the exact frame which I want something to happen, especially if I'm also controlling animation properties in tandem with the state charts potentially firing a transition before the animations get there and having certain things not happen.
At the end of the day it seems like there are many paradigms one could consider (though thankfully Godot is by default more inclined to keep some guard rails in place compared to Unity, which I very much prefer).
I may change my tune as I continue development in this way, so we'll see. I'm just about to start implementing attacks and collisions, which means interrupting existing states and going to a universal "Hurt" state, so we'll see how that goes. :P
I see, thanks a lot for giving me some insight into your process. Calling send_event
from an animation is actually a pretty neat trick!
I recorded a quick Youtube video that walks through the issue. More info below.
When I first installed this plugin, I made the mistake of putting all of my input polling in the
physics_processing
signals, which is not ideal for obvious reasons.I've been going through the process (pun intended) of moving every oneshot-style input to a state's given
_input
signal, and any other inputs that need to be polled every frame into_process
. I'm having issues where the state machine is performing its transitions correctly as it did before, but the behavior is now broken, with states entering and exiting so quickly that an animation is never fired, and states are re-entered almost immediately.EXAMPLE
The Roll State
I have a state,
Roll
, where the player rolls. The animation tree is set to return to the Idle animation once the roll finishes, which is how we check to see if we should return to Idle:And the transition:![image](https://github.com/derkork/godot-statecharts/assets/3878045/0b1121d1-6fe1-413e-8e2f-52c66e667bc7)
The Idle State
_on_idle_state_input(event)
RESULT Fails. State processes transition, goes to Roll for one frame, returns to Idle immediately before an animation fires.
_on_idle_state_physics_processing(delta)
RESULT Works, but is physics_processing, so user input is at risk of being dropped.
_on_idle_state_processing(delta)
RESULT Fails. State processes transition, goes to Roll for one frame, returns to Idle immediately before an animation fires.
Processing input outside of FSM
What is strange is that I get a different result when I poll inputs outside of the state charts.
_input(event)
RESULT Fails. State processes transition, goes to Roll for one frame, returns to Idle immediately before an animation fires.
_process(delta)
RESULT Works. State processing and behavior occurs as intended and expected.
I won't post about
physics_processing
, since that works in the state chart and isn't the solution I want anyway.CONCLUSION
I'm not quite sure what is going on. I have a
Jump
state that also works just fine. I wanted to say thatwas the culprit, because my Jump animation immediately sets a boolean which that state's
physics_processing
checks every frame of the jump (if you're grounded, go back to idle). And that works just fine instate_input
. But this seems less likely when I can use the same logic in a generic_input
function and it runs as expected.I'm pretty lost at this point, and would love some help. Thanks in advance.