Mr Spookydoom

Regarding the finished sword script

So I've been trying for a few hours to come up with my own verison of the sword script before even looking at the finished code as reference or a solution but I had trouble with the order of the commands going into physics process. So after looking at the finished script I was wondering something about the construction of _physics_process() in this case.

If I undersatand this correctly by now - every action that has to be evaluated on every frame has to go into physics process (or _process() for non physics things) somehow, right? Either directly or via separate function that is then called each frame in _phyiscs_process, like the rotate_towards() function in the script.

The problem is that we have to make sure that only a selected set of these commands necessarily need to be executed at a time.

So in other words we are trying to create a cascade of states in _physics_process that evaluates only the part we currently need to evaluate at each given frame, right? Basically a state machine.

if not _target:
return

basicaly is the overall gatekeeper. If there is no target _physics process will be skipped alltogether this frame.

Then everything below that, not sitting in an if-statement is code that will always be evaluated, no matter what other condisions apply, as long as there is a _target present.

func _physics_process(delta: float) -> void:
[...]
_rotate_towards(_target.global_position, 1)
_line_of_sight.cast_to = _target.global_position - global_position

Everything in the if-statements then cascades down to the various stages of what the mob does when the player gets close or into attack range. The outer if-statments differentiates between detection_area and attack_area, the if statements in attack_range differentiate between attacking and preparing the next attack

func _physics_process(delta: float) -> void:
[...]
if _target_within_range:
if _is_in_attack_state:
_velocity = attack_speed * _charge_direction
_velocity = move_and_slide(_velocity)
else:
orbit_target()
_prepare_to_attack()
else:
follow(_target.global_position)

Did I understand that right?

And is this way of thinking a good way to approach constructing code when there isn't a solution to the problem for refrence or to copy from?

Is it OK to nest several if-statements into each other or is there a better way?
I assume at some point this could get quite heavy for more complex decision trees and being evaluated on every frame on possibly many mobs at once?

  • Nathan Lovato replied

    You got everything right! I would just nuance the part about process vs physics process.

    You want every motion that involves physics to be in physics process so that areas and bodies synchronize with the rest of physics in your game.

    But then you can also run animation and other update code in physics process just to keep your mobs codes in one place. You don't have to play sound or animations or other things in process, that's handled for you by the engine.

    Is it OK to nest several if-statements into each other or is there a better way?
    I assume at some point this could get quite heavy for more complex decision trees and being evaluated on every frame on possibly many mobs at once?

    First, the performance is not much of a concern here. It's more things like running heavy algorithms written in GDScript that would be your first bottleneck.

    It is completely okay to nest if statements until it gets in your way.

    The more complex your a mob gets, the more you want to reuse code, and the more you will need a more flexible way to code AIs.

    The most common tool for that in games is using a Finite State Machine, which allows you to get rid of all those booleans.

    Then, there are behavior trees that are much more complex and mostly for larger games with complex AI agents. Behavior trees are especially good for code reuse and offer you a high-level framework to quickly design different complex AIs.

    What you gain in flexibility you generally pay in extra code and maintenance. Behavior trees add a lot of code and complexity to your project. State machines are reasonably easy but still require more code and maintenance than a plain physics process function.

    To learn more, you can check out our guide on state machines: https://www.gdquest.com/tutorial/godot/design-patterns/finite-state-machine/

    It's not as beginner-friendly as this course but you seem to be pretty comfortable already so hopefully it should make sense to you.

    2 loves
  • Mr Spookydoom replied

    That's pretty awesome. Thanks :D
    I think I will have to go through this over the weekend when my brain is working a little better but I was wondering how to create an actual FSM in Godot. ^^