After some trial and error, I went back to the GDQuest version of a multi-node state machine and I've at least managed to get the game running without any errors, but something in it seems to prevent everything from actually executing properly and I can't figure out what it is. From a debugger I made, I can tell that the states themselves are changing on button presses and the like, but I can't get the player to do anything, not even fall.

Just to test it out, I've so far only added Idle, Walk and Air scripts with very simple actions. The State/StateMachine scripts are literally the same as it is on GDQuest. I figured I could go from there and alter things to my needs once I figured out how to work it. State scripts are as follows:

Player:

class_name Player
extends KinematicBody2D

#VARIABLES
export var gravity: int = 4500;
export var maxFallSpeed: int = 2750;

var animState;
var velocity: = Vector2.ZERO;

func _ready():
	animState = $AnimationTree.get("parameters/playback");

Idle:

#Idle
extends State

func enter(_msg := {}) -> void:
	owner.velocity = Vector2.ZERO;

func physics_update(delta: float) -> void:
	if !owner.is_on_floor():
		state_machine.transition_to("Air");
	
	if Input.is_action_pressed("left") or Input.is_action_pressed("right"):
		state_machine.transition_to("Walk");

Walk:

#Walk
extends State

export var walk_speed: int = 1200;

func physics_update(delta: float) -> void:
	if Input.is_action_pressed("right"):
		owner.velocity.x = walk_speed;
	elif Input.is_action_pressed("left"):
		owner.velocity.x = -walk_speed;
	else:
		if owner.velocity.x == 0:
			state_machine.transition_to("Idle");

Air:

#Air
extends State

func _physics_update(delta: float) -> void:
	owner.velocity.y += owner.gravity * delta;
	if owner.velocity.y > owner.maxFallSpeed:
		owner.velocity.y = owner.maxFallSpeed;
	owner.velocity = owner.move_and_slide(owner.velocity, Vector2.UP);

And again, through the debugger, I can tell that the states are being switched out. The starting state is Idle, but my player starts in the air so they fall at the beginning and it does say it's in the air state. If I disable the air state, then it'll say 'Idle' and if I press either movement option, it'll go to 'Walk,' so I know at least that's working, but the player themselves doesn't do anything except stay suspended in the air and play their idle animation on loop. I've double checked the state scripts (both my own and the ones on GDQuest) and as far as I can tell, everything seems fine, so I really don't know why it's not working.

The code described does not seem to do the correct state transitions (Idle, Walk, Air).

A state machine should contain all the conditions for switching from one state to another.

For example) - When you jump from a stopped state (Idle to Air). - When there is no floor at the end of the walk (walk to Air). - When you land on the floor from the air (Air to Idle).

It's hard to understand with text, so I illustrated the original (GDQuest) and your code.

Your code does not describe the conditions for transitioning from Air to other states, and there is no transition_to call.

Please check again when you call transition_to in each state of GDQuest you are referring to.

Summary

  • Review the conditions for state transitions, and try to switch states.
  • Call the appropriate function (e.g. move_and_collide) in the state where movement is possible.

@MizunagiKB said: The code described does not seem to do the correct state transitions (Idle, Walk, Air).

A state machine should contain all the conditions for switching from one state to another.

The states are nested in a 'State Machine' node with the right script attached (the StateMachine script) that has all those functions. Each individual state also extends the 'State' class which calls the StateMachine class. I didn't bother putting those in the previous post since they're exactly the same as the ones on GDQuest and I haven't done any other transitions in the air state since I can't even get the player to fall or do anything that they're supposed to do while in any particular state. And as I've said before, thanks to the debugger I made, they are transitioning, the player just won't do anything.

I'm sorry. The code is abbreviated, and you want to transition from Air to Walk or Idle in some way.

Now I'll write about the problem of not being able to control it. If you read the code, you can see that velocity is set in Walk state, but the move_and_slide function is not called.

First, add a breakpoint or print to the place where you are calling move_and_slide, and check if move_and_slide is called in the walk state, and if the velocity value is passed correctly.

If not, please modify (or maybe add) move_and_slide so that it can be called.

The GDQuest code you are referring to is available as a project on GitHub. https://github.com/GDQuest/godot-design-patterns

How about getting something that works first and trying to run it as is? After that, you may be able to get closer to the cause by re-reading your code.

@MizunagiKB said: If you read the code, you can see that velocity is set in Walk state, but the move_and_slide function is not called.

First, add a breakpoint or print to the place where you are calling move_and_slide, and check if move_and_slide is called in the walk state, and if the velocity value is passed correctly.

If not, please modify (or maybe add) move_and_slide so that it can be called.

Okay, I've added move_and_slide to the Walk script which does allow the player to move. Not sure how I missed it, that's entirely on me.

I'm still stuck on trying to get the player to at least fall while in the Air state (I'll worry about jumping and moving later). The physics code I'm using is the same that I've used before in an old script that worked there and to test it out, I made short test script that also worked just fine. I double checked the GDQuest code on the Player and Air scripts and what I've got isn't any different from theirs as far as I can tell (I did add a transition to Idle if they're on the ground), so I'm stumped as to why gravity isn't working.

I'm glad to hear that you are now able to control left and right.

The next problem is that it doesn't fall when it goes into air state. One possible reason is that the function name defined in Air is _physics_update.

The correct name is physics_update func _physics_update(delta: float) -> void: func physics_update(delta: float) -> void:

The reason is that Walk, which is also based on State, is defined as physics_update, and it can move left and right.

@MizunagiKB said: I'm glad to hear that you are now able to control left and right.

The next problem is that it doesn't fall when it goes into air state. One possible reason is that the function name defined in Air is _physics_update.

The correct name is physics_update func _physics_update(delta: float) -> void: func physics_update(delta: float) -> void:

The reason is that Walk, which is also based on State, is defined as physics_update, and it can move left and right.

Oh snap, it was a typo. Now I feel like a dunce. :#

Thanks so much for the help!

One last thing I've noticed: while grounded and moving, the player rapidly switches between the Walk and Idle states and has a slight jittery movement to them. I added an owner.velocity.y += owner.gravity * delta; to the Walk state which sorta fixes it (it still does it the first second or two while the button's held), but the y velocity stays set to 75.000008 the whole time they're on the ground, only resetting when they go into the air again. It doesn't seem to really break anything, but I am wondering why that's happening.

I'm glad we were able to resolve this.

The remaining problem is that Idle and Walk are switched.

The direct cause of this problem is that the following two calls are being made somewhere. - transition_to("Idle") - transition_to("Walk")

Otherwise, they wouldn't switch, would they?

This is not a direct solution, but the first thing to do is to find out where transition_to("Idle") is being called during the walk state, which is the cause of the problem. Then examine the conditions under which it is called.

Have you accidentally written something like "Air" for air and "Idle" for ground? Or are you calling Idle when none of the conditions are met, or at the end of the program?

As you add more and more features to your game, the state machine will become more complex.

If you have a problem with the state machine, you may want to

  1. Find the location where you are switching.
  2. Find out the conditions under which it is called.
  3. If the condition is unintended, change or add an appropriate condition.

You can investigate the problem by going back to the state, cause, and reason in this order.

I hope you are able to solve the problem.

@MizunagiKB said: I hope you are able to solve the problem.

After doing some testing, I'm pretty sure I figured out what's causing it, but I'm not sure why it's happening. It's transitioning rapidly between the Walk and Air states (I added a walk transition to Air so it can skip Idle if needed). The problem seems to come from the move_and_slide function in the Walk state's physics update.

owner.velocity = owner.move_and_slide(owner.velocity, Vector2.UP);

That along with some code for gravity makes the player's y velocity go to a max of about 75, which it'll then stop jittering until they go airborne again, in which their velocity is reset to 0 and the cycle repeats when they move. I think what's happening is that the Walk state's move_and_slide wants to put the player's y velocity at that particular value and since their technically falling, it goes to the Air state, but since they're also supposed to be grounded, it goes to the Walk state, causing it to go back and forth.

Perhaps, your Walk code only describes left-right movement?

Since we are using a physics simulation You need to follow the rules.

In addition to the left-right movement, you need to give the gravity as follows.

owner.velocity.y += owner.gravity * delta

Strictly speaking, gravity must be given in any state other than Walk or Air.

Just like in the real world, gravity doesn't suddenly disappear when you stop or squat.

It is not a strange behavior that the move_and_slide function makes the object appear to float. This is because the laws of action and reaction are working according to the physics simulation.

@MizunagiKB said: In addition to the left-right movement, you need to give the gravity as follows.

owner.velocity.y += owner.gravity * delta

Not sure if I'm understanding since it has that. In fact, that's what seems to "fix" it, if only temporarily. As stated earlier, this allows to get to that value that move_and_slide is trying to go to. Without it, it goes back and forth between Air and Walk indefinitely.

Strictly speaking, gravity must be given in any state other than Walk or Air.

So, don't put it in those states? I tried putting it in the main player script which actually fixed everything visually, but the y velocity increases rapidly while grounded. Doesn't seem to break anything, but it is concerning.

It seems to be resolved to me.

The place where you are feeling uncomfortable may be the behavior of the physics simulation, or it may be derived from the game you are making.

Either way, the conversation is not about state machines, and I don't have the knowledge to answer about physics simulation.

I can't help you, so I suggest you either learn about physics simulation yourself, or ask a question and find someone who can.

a year later