derkork / godot-statecharts

A state charts extension for Godot 4
MIT License
761 stars 39 forks source link

Question regarding state chart demo project #142

Closed dragoonreign closed 3 weeks ago

dragoonreign commented 3 weeks ago

Beginner state chart user here.

In my project, state chart is starting to becoming difficult to manage with all the states and transitions. Then, I found a sample project within the plugin called "platformer_demo.tscn," and found a mysterious animation handling within the project. Which could probably help reduce the number of states within the project.

Question 1: The main character ninja_frog.tscn has jump and fall animations. I've checked ninja_frog.gd and animation tree but I cant find the animation transitions. Could you help me with how and where the script is handling the animation transition?

Question 2: What is the general rule of thumb when adding or removing a state in the state chart? Example, I have idle and move state but I don't know if these are necessary after looking at ninja_frog.tscn's state chart. Also I was thinking of adding a new states "wall state" to handle wall actions like wall jump and wall slide, also another state called "dash state," but should I just make these Boolean instead of states?

derkork commented 3 weeks ago

Question 1: The main character ninja_frog.tscn has jump and fall animations. I've checked ninja_frog.gd and animation tree but I cant find the animation transitions. Could you help me with how and where the script is handling the animation transition?

The animation is mostly controlled by the animation tree in the ninja frog scene. In line 50 of the script attached to the ninja frog we have:

    # set the velocity to the animation tree, so it can blend between animations
    _animation_tree["parameters/Move/blend_position"] = signf(velocity.y)

This changes a blend position slider based on the current vertical velocity, so it sets it to -1 if we are currently going down, 1 if we're currently going up and 0 if we're currently not moving in the vertical direction (e.g. are on the ground). The blend slider has three animations assigned to it, so that depending on what number we give to the animation the correct animation is picked.

https://github.com/user-attachments/assets/8a53f06b-23ea-43e4-8e5d-262d9ce0d8f9

In addition we have the animation tree states in the state chart which switch between "Idle" and "Movement" states in the animation tree:

image

These are controlled by movement speed in our ninja frog script:

    # let the state machine know if we are moving or not
    if velocity.length_squared() <= 0.005:
        _state_chart.send_event("idle")
    else:
        _state_chart.send_event("moving")

What is the general rule of thumb when adding or removing a state in the state chart? Example, I have idle and move state but I don't know if these are necessary after looking at ninja_frog.tscn's state chart. Also I was thinking of adding a new states "wall state" to handle wall actions like wall jump and wall slide, also another state called "dash state," but should I just make these Boolean instead of states?

This sort of depends on the complexity of rules behind the features you want to add. Basically what the state charts allow you to do is to run logic conditional on what state we're in. If you find yourself writing a lot of if/else conditions in your code this might be an indicator that using a state chart can help here. Another indicator is if you need specifically timed logic to happen. For example the dash is probably something that has a cooldown and only runs for a short period of time. There may also be a rule that says you can only dash while you are on the ground and currently moving. That's a lot of stuff to juggle in code and in this case a state chart can help managing this complexity:

image

With this setup your code mainly sends events responding to input to the state chart and the state chart will mange all the complexity and timing. E.g. there would be an state_input handler on the "Available" state of "Dash availability" which checks if the dash key is pressed. This way dash key presses only work when you can actually dash and you don't have write lots of booleans and helper variables to keep track of all the moving parts - the state chart will do this.

If however a state you want to add has really no logic to it, then it might be it isn't needed. As you rightly point out the Idle and Move states really only trigger the switch in the animation player and as such they do very little and also have no really complex logic to them. We could remove them and just call the animation tree ourselves here:

    # let the state machine know if we are moving or not
    if velocity.length_squared() <= 0.005:
        _animation_tree_state_machine.travel("Idle")
    else:
            _animation_tree_state_machine.travel("Move")

In the example I only added them to showcase the animation tree nodes, but in a real game I would probably not model them as separate states unless there was additional logic needed depending on these states (e.g. like in our dash example before).

dragoonreign commented 3 weeks ago

Thank you for the in-depth explanation answers to the questions! I was intimidated by Animation State Machines and lacked knowledge in the blend slider (didn't realize it can hold three animations with animation keyframes). I will try and refactor the state charts in the project with the answers that was provided! :)

Again, thank you!