How to call a specific node from a script not attached to a node

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

Hi,

I’m trying to make a turn based game where at the start of a player’s turn I want a camera ($Camera2D) to center on the current player.

With the code I’m running below, I get the following error:
"Attempt to call function ‘map_to_world’ in base ‘null instance’ on a null instance.

Here’s my code flow:

Scene:
MapView_test_1.tscn

  • MapView (Node2D node)
    ** Camera2D (Camera2D node)
    ** TileMap (TileMap node)

Top Node: MapView
Attached Script: MapView_test_1.gd
extends Node2D onready var turn_queue_gd = preload("TurnQueue.gd").new() func _ready(): turn_queue_gd.next_turn()

Script: TurnQueue.gd
Attached to Node: not attached to any node
extends Node func next_turn()->void: var player_gd:Node = preload("Player.gd").new() # start the player's turn yield(player_gd.play_turn(), "completed")

Script: Player.gd
Attached to Node: not attached to any node
extends Node func play_turn()->void: # which player? var player_num:String = "player1") # this prints the correct Vector2 map_position: # print(GameConstants.capital_location_dict[player_num]) # move camera to player's start point $Camera2D.set_offset($Navigation2D/Terrain_TM.map_to_world(GameConstants.capital_location_dict[player_num]))


Basically, I’m thinking that since Player.gd is not attached to the tree structure (get_tree().get_root()) it doesn’t know what $Camera2D is.

So my general question is: If you have a gd script not attached to any node, how do you reference specific nodes in a scene (e.g. $Camera2D)?

Thanks for your time,

P.S. I don’t know how to add tabs here, but the code is indented correctly.

Is your player.gd script supposed to be a Singleton?

SIsilicon | 2019-10-17 20:18

No, though I do have a script that is a Singleton (GameConstants.gd) that stores the dictionary: capital_location_dict.

My thought now is that I am structuring this wrong. I was thinking of having one node triggering a script, and then having a bunch of stand alone scripts interacting with each other to handle the logic/flow.

From what I have read, creating and using nodes are “cheap” so I have created multiple scenes:

TurnQueue.tscn

  • which has TurnQueue.gd attached to it
  • and has PlayerTurn.tscn as a child node

PlayerTurn.tscn

  • which has Player.gd attached to it

Now I can simply rotate through player (nodes) for turns and I should be able to access $Camera2D since they are in same tree.

Does that seem like a better approach?
e.g. Better to have many nodes with attached scripts vs. a few nodes with lots of stand alone scripts

eod | 2019-10-17 21:46

Could you reformat your code snippets? It’s kind hard to read.

Just make 4 spaces for each indent.

SIsilicon | 2019-10-17 22:23

:bust_in_silhouette: Reply From: SIsilicon

You could store a reference to the main scene inside of the player.

MapView_test1.gd

var turn_queue_gd = preload("TurnQueue.gd").new(self) # you can pass. variables to scripts when creating them. The script will need an _init function.

TurnQueue.gd

var main

func _init(main):
    self.scene = main

func next_turn():
    ...
    yield(player_gd.play_turn(scene), "completed")

Player.gd

func play_turn(scene):
    ...
    scene.get_node("Camera2D") # Now you can just get the node from the scene. :)
    ...

Cool! Interesting… will have to play around with this.

So in regards to my original error:
“Attempt to call function ‘maptoworld’ in base ‘null instance’ on a null instance”

it looks like my mistake was not referencing the node relative to where my current script existed.

Without having to worry about where my gd_script is on the tree, I was able to successfully reference them this way:

old (error way):
$Camera2D
$Navigation2D/Terrain_TM

new (correct way):
$“/root/MapView/Camera2D”
$“/root/MapView/Navigation2D/Terrain_TM”

Thanks for your time @SIsilicon

eod | 2019-10-17 23:26