Hello everyone!

I've been working on a 2D game and I recently ran into some issues with my usage of Area2D nodes as doors. The way it is meant to work is that when the player enters the area and presses the interact button (E key) the door's script triggers and changes the scene to a different level, setting the player spawn in the process.

This works fine for the first level, where there's only one door, however, any level with two or more doors results in an error when trying to use any of them.

Error setting property global_position with value of type Nil

I've also noticed, after adding a message to be printed when using a door, which prints the name of the level it directs to along with its spawn point, that when entering a level with multiple doors or using a door in a level with multiple doors, all doors in the level print their messages, even though they're not supposed to do that unless interacted with.

I'm wondering if this is because the code for the doors is all based on a single script.

The code uses a Door_base script that is meant to handle the actual logic, with all other doors simply adding their respective spawns and level names:

extends Area2D
class_name Door_base

var LEVEL_NAME = ""
var SPAWN_POINT = ""
var CURR_SCENE = ""

func _ready():
	connect("body_entered", self, "on_body_entered")
	connect("body_exited", self, "on_body_exited")

func on_body_entered(body):
	if(body.get("TYPE") == "PLAYER"):
		#print("Player in!")
		body.door = self

func on_body_exited(body):
	if(body.get("TYPE") == "PLAYER"):
		#print("Player out!")
		body.door = null

func _on_open_door():
	#print("door opened")
	print("Level: ", LEVEL_NAME, " Spawn: ", SPAWN_POINT) 
	Globals.current_scene = CURR_SCENE
	Globals.spawn_point = SPAWN_POINT
	Globals.load_level(LEVEL_NAME)

This is an example of the code each door uses:

extends Door_base

func _ready():
	LEVEL_NAME = "res://Scenes/Farms/Farm_south.tscn"
	SPAWN_POINT = "Farm_south_spawn"
	CURR_SCENE = "Farm south"

What am I doing wrong? Is there a way to get the current method to work or if there isn't, is there a better way to handle this kind of scene switching?

Thank you all for your help.

Hi, I don't see in your code where global_position is being set but what I imagine is happening is that you are setting it twice, once for each door in your scene. And the second time there's an invalid value because you weren't expecting that to happen.

Oops, forgot to include that, sorry. :)

global_position is set in the Player script:

extends Entity

signal open_door

func _ready():
	TYPE = "PLAYER"
	print("ready!")
	var globals = get_node("/root/Globals")
	var spawn = globals.get_spawn_point()
	#print("Spawn point: ", String(spawn))
	global_position = spawn

and is obtained from the following:

func get_spawn_point():
	print(spawn_point)
	if(spawn_points == null):
		print("Null spawn points!")
		return Vector2(0,0)
	else:
		for x in spawn_points:
			if(x.get_name() == spawn_point):
				print("spawn point ", spawn_point, " found!")
				print(x.global_position)
				return x.global_position

That code is part of an autoload script that handles changing scenes and spawn points.

spawn_points is an array of Position2D nodes that mark the places where a player can spawn in a scene.

spawn_point is the name of a specific node as set by the door script.

Thanks for your comment, based on it, I checked around and I think the issue is that each door sets its SPAWN_POINT andLEVEL_NAME values in its_ready() function. I disconnected the open_door signal from all but one door on each scene and it worked fine, the moment I tried connecting more than one, it was back to the same error.

This is the output from having only one door:

and from having three doors:

I had all doors connected at the same time to the Player's open_door signal, and it looks like that caused all doors to activate at once, so I'll have to find a way to make it so that only one door is active at a time in order for it to work.

Thanks for your help!

It looks like get_spawn_point() is returning null which can happen if none of the spawn_points name's match spawn_point. Which is just something else to look at in case that is the issue. Unfortunately I am not familiar enough with the code to tell you what absolutely is the problem but it sounds like you're on your way to figuring it out yourself.

Right, somehow I failed to take that into account, thanks for pointing that out! :)

I'll get to work on fixing that too, thanks again! \m/ \m/

13 days later

Took me a while, but I've now managed to get it to work, unfortunately, I had to abandon the idea of using signals as I could not figure out how to trigger only one of the doors.

Every time the player interacted with a door, the open_door signal was sent to all doors in the scene, causing several issues.

I ended up modifying the player script:

func process_interact():
	if(door == null):
		print("Interacted!")
	elif(door):
		if(door.has_method("open_door")): #added this instead of emitting a signal
			door.open_door()
	else:
		print("Door can't be opened!")

and also the door_base script, replacing _on_open_door with open_door:

func open_door():
	Globals.current_scene = CURR_SCENE
	Globals.spawn_point = SPAWN_POINT
	Globals.load_level(LEVEL_NAME)

The functionality is the same, just that now, instead of emitting a signal when the player interacts with a door, I check to see if that specific door object has the open_door function.

This works for every door, I've tested it already, only the door the player interacts with is used, it no longer tries to open every door available. Not sure how efficient it is performance-wise but at least it works.

Send two signals, one sends the open_door the other fetches and sends the nodes id.

Yeah, that could work, I'm kinda happy with how it works now, but I'll try that method soon, thanks!

2 years later

I guess I'm late but here's my thought:

1- create interaction script you should have a variable to store the current item that'll be interacted 2- interacting only calls the interact function on the variable 3- on area entered and on area exited are implemented here and they set area.get_parent() to the variable or remove it if it's the same. 4- for each Area of the door you connect the signals to the corresponding function on the interaction script 4.2- alternatively you can have the areas not as children of the door but bind the door it opens sending it on an array as an extra argument to connect, in that case all on_area_ signals needs to receive 1 extra argument and all connects needs to receive an array where the first object is the interaction object. 5- then implement the function interact(key) on the objects that you can interact with. 6- on your interaction script now you can just check if there's an interactable object in the variable and call interact with the key pressed

a year later