Can't load player data to nodes in other scenes

Godot Version

Godot 4

Question

I’m getting the error “Invalid access to property or key ‘global_position’ on a base object of type ‘PackedScene’.” how would I load the player data into an enemy node when they’re not in the same scene?

Preload just loads the packed scene, you have to instanciate it, like:

var target = preload(“res//player/player.tscn“).instantiate()

Now target is an instance of the player scene, but that’s not going to work for you either, because it’s a different instance that the actual player in your game.

You have to get a reference to the actual player. You can do that in different way. You can create a singleton (project > project settings > global > autoload) to store variables in it that can be accessed from any script.

While the singleton method works, there’s a much more consistent way to do it in this case. Just right-click the player in the main scene, then select “access as unique name” from the list. Then you can pull the player’s info like this: @onready var player_node: Node2D = %“Player”

This lets you reference all the player’s info without instantiating in code, as you reference the instance that’s already in the scene tree. This can break if the player isn’t always in the main scene though, and you need to keep their name consistent.

As a side note, @mendless you don’t need to nest your player’s CharacterBody2D inside a Node2D and it may cause issues for you later down the line. The Node2D parent won’t move when the CharacterBody2D child moves, so code tracking the location of the player won’t work properly unless the CharacterBody2D is the player’s root node.

You wouldn’t.

What data of the Player could the Enemy possibly need to know? Your actual question here is an example of the XY Problem. You had a problem, and decided that the solution was loading all the Player data into the Enemy. It might help if you told us what problem you were trying to solve that led you to this solution. Because I promise you, there’s an easier way.

Agreed, but if you put the CharacterBody2D inside the Node2D so that you could transform the position, rotation or scale of the CharacterBody2D inside the node, you need to deal with that. In which case I recommend making them to the nodes below it or applying them to the Player node once its in the level scene.

They show the use case in the code block in their screenshot. Mendless is trying to reference the Player’s position so the Enemy can follow it. The issue is, they’ve done this by referencing a fresh Player scene, so it does not have the information they needs as that’s stored in the Player instance in their Main scene.

So if I understand the problem they’re trying to solve, the correct course of action is to find a way to make the Player’s position accessible to the Enemy.

This is a good point, though I feel the need to point out that all his scenes are using a Node2D as a root node (you can see the scene root in the tabs, and the Gun scene grandchilded to the player).

I’ve seen this done by some beginners who don’t know they can change the root node of a scene, and child everything to the default node they were given. I did this when I was first learning Godot as well, as I didn’t know any better. If I’m right then the Node2D roots don’t serve a purpose.

1 Like

Thanks. Good catch. I admit I did not look at the screenshot. The solution in this case is a very easy one: Player Detection.

There are two methods I would recommend. One is only for bullet hell games. The other is for everything else.

Bullet Hell

In this case, you spawn the enemies, they rush the player and die. So just do this:

  • Add the player to a group called “Player”.
  • Add code to the enemy in _ready()
var player: CharacterBody2D

func _ready() -> void:
	player = get_tree().get_first_node_in_group(&"Player")

Then use it.

All Other Games

If you have a game where the Enemy shouldn’t see the Player if they’re too far away, or through a wall, you need a more complex solution.

  1. Add an Area2D to the Enemy.
  2. Rename it DetectionArea. (The name doesn’t matter, we’re just using it for our code below.)
  3. In the Inspector change the Collision Mask for the DetectionArea so it only detects Layer 2. (You can right-click on it to change it to rename it to Player. Choose a different layer if there’s already something on this one.)
  4. Add the Player to that Physics Layer in the Inspector.
  5. Then add a CollisionShape2D to DetectionArea.
  6. Add a CircleShape2D to the Shape in the Inspector. (I also changed the debug color to a yellow.)
  7. Add code to your Enemy script:
var player: CharacterBody2D

@onready var detection_area: Area2D = $DetectionArea


func _ready() -> void:
	detection_area.body_entered.connect(_on_player_detected)
	detection_area.body_exited.connect(_on_player_lost)


func _on_player_detected(body: Node2D) -> void:
	player = body


func _on_player_lost(body: Node2D) -> void:
	player = null
4 Likes

I’m still getting a “null instance” error. I think the problem is I’m not moving node2D but rather the CharacterBody2D.

I would recommend to always look at every error message. One of them should be like 'Node not found: “%Player” '.

A scene unique node can only be retrieved by a node inside the same scene.

If you have a separate enemy scene, but “Player” is a Unique Node in the main scene, you can’t access it (directly) from the enemy script. Try one of the solutions @dragonforge-dev suggested.


This will probably be your next problem, after the current one is fixed. But this would only cause a null reference if you were casting the node to an incorrect node type.

1 Like

Wait, that’s a limitation? I’ve been doing that just fine in my projects.

As long as all the nodes are put into the same scene, like how they’re arranged in both my and OP’s scene trees, it works fine for me. Granted, I have the script tracking the player from the enemy’s (dinosaur’s, in this case) root node. There could be issues with my method that come up if you’re trying to do it from a child node.

(Only tested this in 4.6, not any newer versions.) It seems to work from the scene’s root when the scene got instantiated in the editor, but not when instantiating from script. Not sure if this behavior is intended.

If there were two different nodes with the same %UniqueName - one in the main scene and another in some instantiated scene - you could accidentally access the wrong one.

1 Like

The group method worked for me. Thanks!

1 Like