Newbie trying to figure out State Machines. Getting an error on "Invalid call. Nonexistent function 'change_state' in base 'Node2D'."

Godot Version

Godot V4.1.1stable.official

Question

I’m just dipping my toes into State Machines and am following a tutorial, but am currently getting an error and the game won’t playtest. All scripts are currently attached to a CharacterBody2D node. When giving me the error, the program seems to point to the last line of the “RunState” code. Any help would be appreciated.

Here is my code:

On a CharacterBody2D:

extends CharacterBody2D

var state
var gravity = ProjectSettings.get_setting(“physics/2d/default_gravity”)

func _ready():
state = State.new()
state.name = “State”
add_child(state)
state.change_state(“Idle”)

func _physics_process(delta):
if not is_on_floor():
velocity.y += gravity * delta
move_and_slide()

State”:

extends Node
class_name State

var MAX_SPEED = 8000
var ACCELERATION = 1000
const SPEED = 600.0

var states
var current_state

func _init():
states = {
“Idle”: IdleState,
“Run”: RunState,
}

func change_state(new_state_name):
if get_child_count() != 0:
get_child(0).queue_free()
current_state = states.get(new_state_name).new()
current_state.name = new_state_name
add_child(current_state)

IdleState”:

extends State
class_name IdleState

var player

@onready var anim = get_node(“AnimationPlayer”)

func _ready():
player = get_parent().get_parent()
if get_node(“AnimatedSprite2D”).flip_h:
anim.play(“Idle Left”)
else:
anim.play(“Idle Right”)

func _physics_process(delta):
if Input.is_action_pressed(“ui_right”):
get_parent().change_state(“Run”)

elif Input.is_action_pressed("ui_left"):
	get_parent().change_state("Run")
player.velocity.x = lerp(player.velocity.x, 0.0, 0.2)

RunState”:

extends State
class_name RunState

var player

@onready var anim = get_node(“AnimationPlayer”)

func _ready():
player = get_parent().get_parent()
anim.play(“Run”)

func _physics_process(delta):
if Input.is_action_pressed(“ui_right”):
player.velocity.x = min(player.velocity.x + ACCELERATION * delta, MAX_SPEED * delta)
elif Input.is_action_pressed(“ui_left”):
player.velocity.x = max(player.velocity.x - ACCELERATION * delta, -MAX_SPEED * delta)
else:
get_parent().change_state(“Idle”)

2 Likes

I suppose the error occurs in the line get_parent().change_state(“Run”) or similar. That’s because get_parent() returns the parent node in the scene’s hierarchy, and the scene is probably of the Node2D type.
If you want to call change_state(state) in the superclass, use super.change_state(“Run”)

1 Like

Thanks! This does seem to have solved that error, though I’m unclear as to why. Shouldn’t they both do the same thing if the referenced code is one level up the hierarchy anyway?

Though, now the code is saying that the velocity is undefined but that might be a different issue, I don’t know.

1 Like

I don’t think using super is the solution. “State” is like a state handler that add different states as children, but every state (run, idle…) also extends State so they all have a change_state function. That is what you call using super but that’s not what you want. You do want to call the parent’s (the state handler’s) change_state.
I might be wrong though, it’s a little confusing :slight_smile:

Can you check what print(get_parent()) prints before the line that gives the error?

1 Like

It’s giving me:

Character 01:<CharacterBody2D#37111203006>

Oh, that’s the velocity error. Let me swap it back and see what it prints on the original error.

The get_parent() error is giving me

Node2D:<Node2D#37329306818>

Well I don’t know :\ The parent should be Node since that’s what State extends, not Node2D.

The velocity issue is probably also because of this:
player = get_parent().get_parent() doesn’t get the player node (that has the velocity variable) because the node hierarchy is somehow incorrect.

But I don’t see in the code why. Again I might be completely off though…

Well, crud. Thanks for looking though. Still good to know where the problem seems to be stemming from. Maybe I’ll shop this around a few other places see if someone knows why the node is wrong.

Okay, so I added a print call during the ready function for the characterbody2D right after when it was supposed to create the node it uses for the other scripts. It didn’t show up, so it doesn’t seem to be running that code, right? Then maybe that’s why it’s getting confused about the node? Because it’s not running the code to make it?

Is it for sure that the code for the CharacterBody2D will run before the rest? If not, how would I make sure it is? I wonder if that might be the issue?

_ready is run once the node enters the tree. So the only case in which you wouldn’t see the print is when you never actually add your CharacterBody2D to the scene. But if that was the case, then your other issues shouldn’t occur either – since you don’t create the states to begin with.

I reduced your scripts to just the part relevant to the state switching and couldn’t reproduce your error:

extends CharacterBody2D

func _ready():
	var state = State.new()
	add_child(state)
	state.change_state("Idle")
extends Node
class_name State

var states = {
	"Idle": IdleState,
	"Run": RunState,
}

func change_state(new_state_name):
	if get_child_count() != 0:
		get_child(0).queue_free()
	add_child(states.get(new_state_name).new())
extends State
class_name IdleState

func _physics_process(delta):
	print("Idle")
	if Input.is_action_pressed("ui_right") or Input.is_action_pressed("ui_left"):
		get_parent().change_state("Run")
extends State
class_name RunState

func _physics_process(delta):
	print("Run")
	if not (Input.is_action_pressed("ui_right") or Input.is_action_pressed("ui_left")):
		get_parent().change_state("Idle")

Hmm… Yeah, you’re right. I dunno, is it possible it’s running the scripts out of order in some way? I tried inputting just what you had here without any of the velocity or movement data and it’s still giving me the same error on basically the same line. It’s trying to connect with a Node2D even though the script is referencing a Node.

I’m at a loss. I’m inputting the code exactly how it is for other people and only I seem to be getting an error.

If you upload your project somewhere and share the link here, I can take a look. :slight_smile:

1 Like

Okay, here is a link to the project. Lemmie know if this works: