How to reference a node in the main scene from a different scene?

Godot Version

4.3.stable

Question

Hi, I’m trying to reference the player’s position in the main scene from an enemy scene, so when the enemy is instantiated it will know where to move to find the player. For testing purposes, I have not yet written any movement code, I am just trying to make the enemies look at the player’s postion and track it in real time so I can verify I am referencing the correct position before moving working on the movement. I have tried a few methods I thought would work, and none of them have come close. I’ll outline some of m attempts below.

The player is a pilot, so I named the node “pilot”, and the enemy is called “creep”. The main scene is simply “main.tscn”.

Attempt 1:

var main_scene = preload("res://main.tscn").instantiate()
var target = main_scene.find_child("pilot").position

func _process(delta: float) -> void:
	look_at(target)

Failed due to the main scene returning null when instantiated, I tested using .can_instantiate with this and other scenes, and determined the scene assigned as “main” cannot be instantiated.

Attempt 2:

var player = preload("res://pilot.tscn").instantiate()
var target = player.find_child("CharacterBody2D").position

func _process(delta: float) -> void:
	look_at(target)

Similar to first attempt, but now referencing a node in the player scene directly. This one successfully instantiated, but I determined I was using .instantiate() incorrectly as this did not reference an existing player, it created new pilot scene every time an enemy spawned. However, they did successfully track the pilot that was created in this way, though the movement script attached to the new pilot scenes would not work.

Attempt 3:

var player = NodePath("/root/Pilot")
var target = player.position

func _process(delta: float) -> void:
	look_at(target)

This one fails due to NodePath() returning as a string and not referencing the “pilot” node itself. I also replaced the path with NodePath("/root/Pilot:position") and tested it with print_debug(), it seems I misunderstood how nodepaths are intended to be used, they are just the path as a string.

Attempt 4:

@onready var player = get_node("/root/Pilot")
@onready var target = player.position

func _process(delta: float) -> void:
	look_at(target)

“node not found” error. Godot simply cannot find “/root/Pilot”.I checked the capitalization, “pilot” is capitalized in the scene tree when it shouldn’t be, but that’s also how it’s referenced in the code so that’s not the problem. I will make the capitalization of nodes consistent after getting the player tracking working.

Attempt 5:

@export var player = Node2D
var target = player.position

func _process(delta: float) -> void:
	look_at(target)

Attempted to manually place a creep into the main scene, and use @export to manually select a node for it to look at. This failed due to “Invalid access to property or key ‘position’ on a base object of type ‘Node2D’.” var target = player.transform.position resulted in a similar error, except it couldn’t access the transform.

At this point I am stumped and don’t know what else to try, all the tutorials I’ve found are either from earlier versions of Godot and do not apply to this project, or do not reference nodes between scenes. I’ve looked everywhere and can’t find any up to date information on how to do this, though it’s likely I just don’t know the correct keywords to use to find what I’m looking for. I have also asked friends who have used earlier versions of Godot, they all recommended I use the method I tried in attempt 1, and were stumped when I explained that doesn’t work.

To reiterate, what I’m trying to do is track the player’s location in the main scene. The enemy that needs to do this starts in a separate scene, and is instantiated in. So the enemy needs to instantiate with a script that returns the player’s position. So far, I have tried methods to reference the nodes in the main scene directly, and reference nodes in the player scene. Neither option has returned a valid position for the player node in the main scene.

What am I missing? I feel like doing this shouldn’t be this hard, so I must be missing an important detail.

Any help you can provide would be greatly appreciated, thank you.

1 Like

Thank you for the suggestion, but unfortunately that thread does not address my problem. While yes, I am trying to reference the main scene in order to track the player, that is not the only reason why I want to try and reference a node in the main scene from a different scene.

I am designing a game and the game needs an AI controller that needs to find and track not just the player, but several other key locations in the main scene. The issue is, the AI controller is a script, ideally it will be managed by a global script and will not need to be present in the main scene to function.

Without being able to read positional data that’s in the main scene from a script external to the main scene, I cannot do this. I also can’t implement the solution you’ve suggested as that would involve me making a large area collider in the main scene and using that to find the positional data of the player; the problem being this information would still be in the main scene, and the script still can’t access the information. That puts me back to square one: How to reference a node in the main scene from a different scene?

So far, all my attempts to pull information from the main scene have returned “null”. I can successfully pull the information I want from non-main scenes, but every time I try to reference the main scene, it’s some flavour of “null”. Is the solution just not using the main scene? Why is information that’s easily readable in other scenes always read as null in the main scene?

Right now I’m thinking of ditching the main scene, having the game run in a different non-main scene, and pulling any information I need from there. If the problem I’ve run into is “the nodes in the main scene are protected and always return null”, then the solution is not using the main scene at all, since it doesn’t work for my application.

The solutions I’ve found are all along the lines of “make a new scene filled with referenceable nodes and use that to store all your static player information” or “use a global or autoloader script to save all your static player information”. That would work if the information I needed to save was static; what I need to pull from the main scene is dynamic, changing information.

This feels relevant to your problem Scene organization — Godot Engine (stable) documentation in English

I may have misunderstood, but it seems in your case you can pass a reference of the player directly to the creep. There is no need to try and find the player. For example:

main.tscn

var player = preload("res://pilot.tscn").instantiate()
func spawn_creep() -> void:
    var creep = load("res://creep.tscn").instantiate()	
    creep.target = player
    add_child(creep)

creep.tscn

var target
func _process(delta: float) -> void:
    assert(target, "no target set!")
    look_at(target.position)

I’ve not tried this, but if you did need to find things dynamically, then you might be able to use the SceneTree (get_tree()) and adding nodes to groups? :thinking:

2 Likes

Thank you, I’m going to try your solution after dinner. The Scene Organization page you linked is helpful, though I do have to note this advice from the page: “If at all possible, you should design scenes to have no dependencies.” It’s telling me not to do exactly what I’m trying to do. The documentation also does not list any method of making a global signal which can be referenced externally to the main scene.

Without testing it, I think your code would work fine inside the parameters of the prototype I’m making, and let me start work on the other features my team wants to see tested. Beyond the prototype, I think I need to use this: Creating a Global Signal System in Godot – Josh Anthony

I found that article just before you posted your reply and have yet to test the system, but it seems like it does what I need. If Josh Anthony’s system works, then I’ll be able to send and retrieve the positional data I need, as well as any other info I might want to use; like the player’s health, speed, what they are armed with, and whether or not they are driving a vehicle.

Thank you so much for the help <3

Thank you everyone for your support, I have finally got this working.

All I needed to do was create a “Global Signals” script, also known as a “signalbus” script, and add that as a global. Then any time I need to reference information across scenes, I can emit a signal to the global script, and call it from the global script with .connect(). There’s still a couple bugs with using this method as the globals are called before _ready() so all the positions default to zero, but I’ve found a work around that will be good enough for now.

I found the solution through this video but you can also find a writeup of the same method in the oldest comment in this docs article.

For posterity, I will reexplain the instructions those sources describe below:

Step 1: create a global

go to project > project settings > globals. From here select the box next to “add” and type the desired name of your global, my recommendation is “Global_Signals”. Make sure it extends Node, and remove all other code except extends Node.

Step 2: Define a signal

In your new global script, type signal followed by the signal name, variable name, and input/output for your desired signal. It should be formatted like this: signal signal_name(variable_name: output).

Example:

extends Node

signal player_position(location: Vector2)

Step 3: Emit the signal

Go to the source of the information you need to reference and attach a script to it, if it already has a script then feel free to just edit that one. Type ScriptName.emit_signal("signal_name", info) into a function, omitting any underscores in the script’s name. If the information needs to be updated in realtime, you will need to insert this into _physics_process().

Example:

extends CharacterBody2D

func _physics_process(delta: float) -> void:
	GlobalSignals.emit_signal("player_position", position)

Step 4: connect the signal

A bit more finnicky than the other steps, so I will divide it into sub steps.

  1. Attach a script to the node you need to receive the information (or edit its existing script).
  2. Define a variable of the same type as the output of your signal.
  3. In _ready() type ScriptName.Connect("Signal_Name", Callable(self, "_function_name))
  4. Define a function with your chosen function name from the previous step with the signal’s variable name and output as arguments func _function_name(variable_name: Output)
  5. make the variable defined in step one equal the variable defined in the signal.

example:

extends Node2D

var target: Vector2

func _ready():
	GlobalSignals.connect("player_position", Callable(self, "_track"))
	
func _track(location: Vector2):
	target = location

Once all this is done you should have a signal that can be referenced across scenes.

The only snag is the signal will likely be set to the defaults for its data type as it will be called before _ready and may not update correctly. This problem seems to be caused by the global loading before the scene tree, and therefore the signal is called before the emitter is loaded. I have tried emitting the signal in _init and _enter_tree, the global is still too fast, and as signals cannot be defined with @onready the global cannot be slowed down.

The workaround I found was to set the values to default in the inspector, then define them in _ready. This is not an ideal solution, but ensures your information is synced between the node emitting the signal, and the global.

Thanks again to everyone who helped me! In the end I think this was just an issue of me trying to run before I could walk, missing an easy process due to not fully understanding the nuances of Godot.

1 Like

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