derkork / godot-statecharts

A state charts extension for Godot 4
MIT License
754 stars 38 forks source link

Struggling a bit with state chart principles and Godot #136

Closed eobet closed 3 weeks ago

eobet commented 1 month ago

I'm trying to implement a state chart with something like the following states:

Now, what makes this difficult for me to grasp are two things:

So, during movement, if the player presses the rotation key and starts a rotation tween, but doesn't release the movement input key, when the rotation has finished I need to go back to the movement state rather than the idle state or if the player has pressed the rotation key more than once this should start a new rotation (in the direction of the last rotation key pressed). I saw that history pseudo-states exist, but I can't figure out what compound states I need to set up in addition to the ones above in order to make that work (if that indeed is the preferred solution here).

In other words, I feel like my goal is to have as much of this logic as possible in State Charts, but it seems as if I must duplicate some of the state functionality with boolean flags in my code to track input actions, which somewhat defeats the purpose of the plugin (thought not really, but I hope you see my point... which is, I'm having difficulties figuring what to put in the state charts and what to put in code).

Is it possible to set this up without any duplicated state/input tracking in my code?

Sorry, is there a better forum for questions like this?

PS. I must also reveal that I'm using your plugin in conjunction with another, which eliminates the use of the expression guard for me entirely...

smile-coder commented 1 month ago

In my opinion, the three states of 'Idle, IsMoving, IsRotating' should actually be one state: Controllable. While in this state, the player's position and angle adjust according to the controls of the handle.

Then, you can add a state: Uncontrollable.

The purpose of Statecharts is to simplify a problem, not to complicate it.

The rest of your logic is, you decide what to do by judging the character's velocity vector which is the result of input from the handle.

I hope it helps you:)

eobet commented 1 month ago

Thank you for the tip!

To reframe what you said, I renamed the "Uncontrollable" state to "TweenedMovement".

Now, my problem was still that during the TweenedMovement state, the player might issue input for moving, rotation or stopping, and I couldn't transition those until the tweening had finished, but the only delayed transition available is on a timer (and again, a new player input might happen before that timer runs out).

So, what I ended up having to do was to cache the last send_event stringname until my tween issued the finished signal, and then I sent whatever was in my cache to the state chart.

So, the chart is mostly readable now, with the implicit understanding that anything which happens in TweenedMovement is cached and delayed (and I actually did manage to tweak the behavior by just altering transition to and events and not code, to finally get it working like I wanted).

image

EDIT: Nope, found a corner case where the above doesn't work... however, if I would be able to send a signal which manually changed the memory of the history state, then I think I would be covered. 😓

derkork commented 1 month ago

How about this:

image

Your rotating state could have three substates which model the currently pending input. When you go into rotating, you start with the "nothing pending" substate. If the user presses rotation / movement input in this state you go to the respective "pending rotation" "pending movement" sub states. Then when the tween finishes, you transition to the pending state. I'm not sure how you want to handle multiple inputs during the tween's runtime (e.g. player first presses rotate, then walk). This example would use the first input, but you could add a few more transitions to use the last input of the player.

eobet commented 1 month ago

Thank you very much for this, I think I understand the concept a little better now.

I'm not sure how you want to handle multiple inputs during the tween's runtime (e.g. player first presses rotate, then walk). This example would use the first input, but you could add a few more transitions to use the last input of the player.

Yes, that's why I wished to be able to manipulate the historystate, because then I could flip which non-tweened movement type I should switch to if multiple inputs arrive during a pending tweened movement.