Need Help on Boss's State not changing and not moving when player entered

Godot Version

<v4.2.2>

Description
Hi guys I’m following a tutorial(https://www.youtube.com/watch?v=otHfaomtJh0&t=575s) on making a golem boss and i am stuck at 9:49 / 18:09 because my boss is not following the player
1)I have a CharacterBody2D named BossGolem that is equipped with the script that named BossGolem.gd and script is like this:

BossGolem.gd:

extends CharacterBody2D

@onready var player = get_parent().find_child(“Player”)
@onready var sprite = $Sprite2D

var direction : Vector2

func _ready():
set_physics_process(false)

func _process(_delta):
direction = player.position - position

if direction.x < 0:
	sprite.flip_h = true
else:
	sprite.flip_h = false

func _physics_process(delta):
velocity = direction.normalized() * 40
move_and_collide(velocity * delta)

  1. CharacterBody2D(BossGolem) has a child node of node2D named FiniteStateMachine that also has a few child node of node2D named Boss_Idle, Boss_Follow and Boss_MeleeAttack. Each child node of FiniteStateMachine is the state of the boss and each of them had attached a script named Boss_State.gd.

Boss_State.gd:

extends Node2D
class_name State

@onready var debug = owner.find_child(“Debug”)
@onready var player = owner.get_parent().find_child(“Player”)
@onready var animation_player = owner.find_child(“AnimationPlayer”)

func _ready():
set_physics_process(false)

func enter():
set_physics_process(true)

func exit():
set_physics_process(false)

func transition():
pass

func _physics_process(_delta):
transition()
debug.text = name

  1. For the Boss_Idle node(State), its script has extended with the script named Boss_Idle.gd and Boss_Follow node(State) is also extended with the script named Boss_Follow.gd.

Boss_Idle.gd:

extends State

@onready var collision = $“…/…/PlayerDetection/CollisionShape2D”
@onready var progress_bar = owner.find_child(“ProgressBar”)

Called when the node enters the scene tree for the first time.

var player_entered: bool = false:
set(value):
player_entered = value
collision.set_deferred(“disabled”, value)
progress_bar.set_deferred(“visible”,value)

func transition():
if player_entered:
get_parent().change_state(“Boss_Follow”)
print(“hui”)

func _on_player_detection_body_entered(_body):
player_entered = true
print(player_entered)


Boss_Follow.gd:

extends State

func enter():
super.enter()
owner.set_physics_process(true)
animation_player.play(“Boss_Idle”)

func exit():
super.exit()
owner.set_physics_process(false)

func transition():
var distance = owner.direction.length()

if distance < 30:
	get_parent().change_state("Boss_MeleeAttack")

  1. The function in Boss_Idle.gd has a function named “_on_player_detection_body_entered” and it is to detected whether the player has entered the collisionShape of the CharacterBody2D(BossGolem). Furthermore, the function has been linked with a Child node of CharacterBody2D(BossGolem) that is an Area2D named PlayerDetection and the child node of PlayerDetection is a CollisionShape2D that is for detecting players.

5)CharacterBody2D(BossGolem) has its collision layer on Layer 2 and have no mask while Area2D(PlayerDetection) is on Layer 2 as well but no mask layer. As for the CharacterBody2D(Player), its collision Layer is at 2 and the mask is at 1.

My CharacterBody2D(BossGolem) node placement:

image
image
image

##Question
The reason I describe all of these to is to solve why my CharacterBody2D(BossGolem) could not change the Boss_Idle state to Boss_Follow stage but instead change to Boss_MeleeAttack when player is not even very close to it, the CharacterBody2D(BossGolem) could not move around the screen but stay static? Also my level and tilemap that has its collision layer and mask layer set at level 1 and 2.

Any help would be great, Thanks!

Can you upload a copy of your project to github? If you do, I will run it and take a look.

Also, have you set any break points to check and see what is happening in the code while it is running?

Sure thing, wait a moment

1 Like

Hi there, this is the copy of my project, it’s in this repository and it’s on the branch named testbranch.
No, sorry i didn’t set any breakpoints, because i don’t know where is the problem.

Forgive me but i need to go to sleep now, thanks for the help kind stranger

1 Like

I have picked a little, and I will pick more later. But will share what I have found now.

First, your error messages are being flooded with messages about move_up and move_down being missing. So, I added them just to get rid of the error spam. Even though you clearly intend to not use it.

After that there was an error about trying to connect a non existant signal. So I commented out that line of code since it appeared the correct connection is connected via the inspector.

No more errors - good. But didnt solve the issue - bad.

The “follow” behavior is controlled by the physics process in one of the scripts. Places in the code turn it off and on. The process gets ultimately turned off and never turned back on. Why? I dont know.

Commenting out all of the code that turns it off so I can test the behavior.

Now the boss follows, the code seems like it intends to call a state change when the distance gets less than 30. However, this does not happen when the distance is less than 30. Why? I dont know yet.

But the issue seems to be causing the states not to change at all.

1 Like

Dear nutlike4693,

Would you please show the changes you’ve done the code for me to take as a reference?

Thanks a lot!

Yes, I will for sure do that. It will just have to wait until I have a break. I can push the changes to your github as well.

I was pressed for time today, so I did a rush job to give some feedback.

I might have some time tomorrow, but not today.

1 Like

Sure thing, Thanks a lot

1 Like

HI there, nutlike4693
Would you please show the changes you’ve done the code for me to take as a reference that you promised?

Thanks a lot!

1 Like

Ok, I found the bug. Let me gather my thoughts and I will post here in a few.

First, let’s get to the actual bug that causes your boss to not follow, then I will share some other thoughts.

The boss doesnt follow, because, for whatever reason, it immediately switches to melee attack upon loading.

(EDIT: Figured out why it switched to melee attack. See follow up reply)

Then there is no code in the Melee state to switch back to follow (as you havent implemented melee yet and are just using Boss_State.gd for now). So I changed the transition code in the Boss_State to this:

func transition():
	var distance = owner.direction.length()
	
	if distance >= 30:
		get_parent().change_state("Boss_Follow")

Now, after the scene loads, the boss switches out of melee to follow.

Further thoughts and notes:

So, before I could start unraveling this, I needed to get rid of the 1000’s of errors being thrown to see if it was some runtime error. Many errors were being thrown due to move_up and move_down not being defined. So, I did the following inside player.gd

func move(delta):
	# renamed to ui_up and ui_down to get rid of errors
	var input_vector = Input.get_vector("move_left", "move_right", "ui_up", "ui_down")
	# hacky: zero out vertical input since we only want left and right
	input_vector = Vector2(input_vector.x, 0)

After that there was a single runtime error complaining about connecting a signal. The code looked unneeded to me as the signal was connected properly in the inspector already. So I just deleted it. In boss_idle.gd

func _ready():
	# commenting this out, seems like signal is already connected properly via inspector
	# collision.connect("body_entered", Callable(self, "_on_player_detection_body_entered"))
	pass

After that, there were no errors, but it didnt fix the issue. Now, I started adding debug statements so I could monitor the output to figure out what was happening.

I added them to the finite state machine script as follows:

extends Node2D

var current_state: State
var previous_state: State

func _ready():
	current_state = get_child(0) as State
	previous_state = current_state
	current_state.enter()
	print("FSM: initial state: ", current_state.name)

func change_state(state):
	print("FSM: call for state change: ", state)
	current_state = find_child(state) as State
	current_state.enter()
	print("FSM: state changed: ", current_state.name)
	
	previous_state.exit()
	previous_state = current_state

Now when I monitored the state changes via debug console print statements, I could see that the boss was correctly starting in idle, but then switching to, and staying in melee. Rather than untangle why it switched to melee when initializing, I checked to see if there was logic in the melee state to change back to follow. Thats when I discovered there wasnt and added the fix at the beginning of this post.

(EDIT: I ended up untangling it. See follow up reply)

So, my advice going forward is to clean up those errors so you can see if a runtime error is causing problems. And use print statements to help you track what is happening.

Good luck out there.

Edit: I also moved the boss down to the ground level so I could walk near to him and walk away to help with my debugging.

1 Like

Ok, now I see you want to have the boss state change to trigger upon the player entering the collision area…I got this figured out as well. Give me a few to polish and post.

Update:

Okay, for fixing the script so the Boss Follow triggers when the player enters.

The issue is, the player detector immediately detects other collision objects and switches out of idle. So, the boss is never in the idle state. I am sure you can filter this with mask layers and stuff (that is probably the right way to do it) but it is not what I did here.

I noticed the “player_entered” text was printing twice immediately and then not printing when the player got close.

I found the problem by added the following print statements in the idle script:

func _on_player_detection_body_entered(body):
	print("Idle: collision detection: ", body.name)
        # rest of your code followed here

Note that I put “Idle” at the front of the print statement? I did that so I can tell what script is printing when I have many print statements.

This immediately showed this:

Idle: collision detection: BossGolem
Idle: collision detection: MainPlatform

That lead me to understand the issue. It was detecting the wrong thing, switching out of idle, and therefore never detecting the player because it never goes back to idle.

So I added this (please note you should probably do this using collsion masks, but I am rushing).

func _on_player_detection_body_entered(body):
	print("Idle: collision detection: ", body.name)
	if body.name == "Player":
		player_entered = true

Now, the Boss stays in Idle until the player gets close.

This is also why the boss was coming out of Idle in the first place. I didnt understand that issue when I was debugging before and thought it was some weird initialization quirk.

Ah, now that made sense. I appreciate your help bro, Thank you so much! It really helped me a lot!!

1 Like

Glad I could help.

I am 99% sure you could do that second fix by setting your collision masks up different.

So I would suggest seeing if you could find a good tutorial on those so you can set them exactly as you will need them.

1 Like

To fix it with the collision layers/masks think of it this way:

Layers are what the thing is

Masks are what the thing can see/collide with

With that in mind, I am pretty sure that:

Player:
is player; sees ground

MainPlatform:
is ground; sees nothing

Boss:
is nothing; sees nothing

Player Detection (inside boss scene):
is nothing: sees player

I am pretty sure that would fix it instead of using

if body.name == "Player":

Edit: but that is me guessing on how you want things to behave. for instance, I assume you want the boss to be able to fly through the ground.

That might’ve been better, I was playing around with the mask layer before this. Sometime it worked, sometime it don’t. Just me playing around with the collision and mask layer of the tilemap, but player.name =“Player” would just focus the detection on the player right?

Flying through the ground was not my intention though, yeah hahah

Yes, it does work to filter it that way.

The layers and masks are a built in way to do the same thing.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.