Connect signal to instance in packedscene?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Andromachus

I’m following Chris Bradfield’s tutorial from “Godot Engine Game Development Projects: Build Five Cross-Platform 2D and 3D Games With Godot 3.0” (specifically the tutorial on the ‘Escape the Maze’ game in chapter 3), and I’m having a problem implementing a proposed turn based movement system for the enemies. The main issue I’m running into is connecting a signal from my player to an enemy that is loaded into the level by a PackedScene variable. The goal is to have the player emit a “moved” signal after each move as shown in the code snippet below (both the player and enemy inherit from Character.gd if that is of any help):

extends "res://Characters/Character.gd"


signal moved
signal dead
signal grabbed_key
signal win

func _process(delta):
	if can_move:
		for dir in moves.keys():
			if Input.is_action_pressed(dir):
				if move(dir):
					emit_signal('moved')

The enemy is supposed to listen for the moved signal and then move randomly in the room, as shown in the following code snippet:

func _on_Player_moved():
	if can_move:
		if not move(facing) or randi() % 10 > 5:
			facing = moves.keys()[randi() % 4]

As is, I cannot figure out how to connect the move signal to the enemy without having an extra instance of the enemy as a child of the level node, which introduces an enemy that isn’t being called into the level as the others are. I’ve tried using other methods, such as:

$Player.connect("moved", self, "_on_Player_moved")

In the enemy code, but that brings up a null instance error. Is there another way to connect these two, or am I going about this the wrong way? Or am I not understanding some aspect of the code? Any feedback would be greatly appreciated.

:bust_in_silhouette: Reply From: kubaxius

Your second solution should work just fine. I think that this error that you’re getting is because of $Player expression – this is a shorthand for get_node(“Player”) which returns child with name of Player, and Player is probably not the child of Enemy, so it returns nothing.

You can try getting Player via something like get_parent().get_node(“Player”), or even better option would be to put him into “player” group, and then access him via get_tree().get_nodes_in_group(“player”). This way you will be able to access Player from any point in the scene tree, no matter where in it he exists.

I tried adding both getparent().get_node(“Player”) and get_tree().get_nodes_in_group(“player”), but the debugger gives me a warning that reads “get_node: Node not found: Player”. This causes a game crash as it still thinks its trying to connect a null instance. I inserted the code into the ready function of the enemy as shown here:

func _ready():
can_move = false
facing = moves.keys()[randi() % 4]
yield(get_tree().create_timer(0.5), 'timeout')
can_move = true
get_parent().get_node("Player")
#get_tree().get_nodes_in_group('player')
$Player.connect('moved',self,'_on_Player_moved')

(I commented out the other method to see which one would work.) It seems that because the player and the enemy are not in the same scene tree (enemy is called in as a script variable and doesn’t exist in the scene tree before the level starts, at least I think that’s what is happening.) the enemy is trying to execute this code before it can exist in the scene tree as the player and returns null. I’m unsure if this is an issue of improper code placement on my part or if I should make the enemy a child of the level scene but figure out how to delete it from the scene before the enemies are spawned into the level proper, but that seems inefficient and I’m sure there’s a better way.

Andromachus | 2020-03-07 11:57

ready function is called only when the node enters the tree, so not really. The problem is different - get_parent().get_node(“Player”) returns a node reference, so you have to store it in a variable. it should look like this:

var player = get_parent().get_node("Player")
player.connect("moved", self, "_on_Player_moved")

get_nodes_in_group returns array of nodes, so you have to pull your Player node from this array:

var players_array = get_tree().get_nodes_in_group('player')
var player = players_array[0]
player.connect("moved", self, "_on_Player_moved")

As I said, $Player is the same as get_node(“Player”), so in your code, you just done this:

# this is completely ignored by code
get_parent().get_node("Player")

get_node("Player").connect('moved',self,'_on_Player_moved')

kubaxius | 2020-03-07 12:20

Oh I see, that was my mistake. I didn’t think to store it as a variable. I’m still relatively new at coding so I’ll definitely keep that in mind in future projects. Thank you very much for your help!

Andromachus | 2020-03-07 16:36