Accessing exported variables of a parent node from a child node

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

I have just only started using Godot. So I am trying to implement a FSM - where each state is a separate node. The tree structure looks something like this:

Player (KinematicBody2D)
-States (Node)
–Idle (Node)
–Run (Node)
–Jump (Node)

So, I am setting all the motion related variables (run speed, jump speed, etc.) on the top ‘Player’ node by attaching a script to it - Player.gd - and it looks something like below:

export (float) var max_speed = 100.0
export (float) var acceleration = 50.0
export (float) var jump_speed = 25.0
export (float) var gravity = 80.0

Now I have a parent script for all motion related stuff - Motion.gd - where I am trying to access the above variables, setting them as below:

var Player = preload(“res://scripts/player/Player.gd”)

var player = Player.new()

var max_speed = player.max_speed
var acceleration = player.acceleration
var friction = player.friction
var gravity = player.gravity

Now I have seen that if I don’t ‘new it up’, I get null references. And if I new it up, I get whatever I have set in the script and I don’t get the adjustments I have made through the inspector for the export variables. What would be the right way to do it? Also, is this even good practice? (The primary motivation behind it is being able to tweak motion variables from the inspector by the top level node, instead of having to expand children and going to each state and tweaking variables).

Thank you!

:bust_in_silhouette: Reply From: Zylann

If your states are all nodes, there is no point in which you should use .new(). You should not use new just to solve null errors, new creates a new instance of a script, it does not add it to the tree. As I understand, your nodes already exist in the tree so you want to access that instead.

Now I have a parent script for all motion related stuff

I thought the top of your tree was Player?

The primary motivation behind it is being able to tweak motion variables from the inspector by the top level node, instead of having to expand children and going to each state and tweaking variables

Ok so it seems your hierarchy written above wasn’t complete then. I guess it’s more like this:

- Motion
	- Player (KinematicBody2D)
		- States (Node)
			- Idle (Node)
			- Run (Node)
			- Jump (Node)

About motion.gd:

var Player = preload("res://scripts/player/Player.gd")

var player = Player.new()

var maxspeed = player.maxspeed
var acceleration = player.acceleration
var friction = player.friction
var gravity = player.gravity

As I said, you should not use new if the player exists already. Use get_node() instead. But because child nodes are only accessible after the scene is ready (i.e when _ready is called), you should use onready so the variables will initialize properly:

# "Player" is the path to the Player node relative to Motion
onready var player = get_node("Player")

onready var maxspeed = player.maxspeed
onready var acceleration = player.acceleration
onready var friction = player.friction
onready var gravity = player.gravity

Accessing exported variables of a parent node from a child node

Now this may have confused me here but if motion.gd is solely to store variables and not modify them, I see no point in using export in the player node, if you actually intend to edit them on that parent node instead…

You can access nodes upwards:

# In player.gd
var motion = get_parent()
print(motion.maxspeed)

Or, if you are in the Idle fsm script:

# In player.gd
var motion = get_parent().get_parent().get_parent()

However as you can see it becomes quite hidious. If the hierarchy changes your script will no longer work. You could use get_node("/root/Motion"), but that still assumes Motion is the parent of everything, and as I said you may want to have more things in your game than just motion, right? Or you’ll have to rename that node to be “the root of all the things” which isn’t really a modular way of thinking (that node could become too big) and making new scenes will still be a pain.
You might consider using autoload singletons eventually, which are available anywhere from code regardless of the scene: Singletons (Autoload) — Godot Engine (stable) documentation in English

If you are just concerned about having to expand children to access your player stats, why not save your player as a separate scene? You can then open that scene and tweak your stats there on the root. Then it will reflect to every other scenes in which you instanced the player.

is this even good practice?

It can be subjective depending on the project. There is nothing wrong for a parent to access its children, but getting its parent is more of an exceptional situation. The main reason why it’s not recommended is because it breaks encapsulation of your node. It now needs “context” from elsewhere, which makes it impossible to test in isolation, and harder to re-use in different scenes.
I would have expected motion to be handled directly on the Player node instead (since it’s a kinematic body responsible for its physics), or on a component (child) of it, because that’s where it actually is used. It’s totally OK to have to click on the player node to modify properties of… the player xD

Hello! Thank you for the detailed answer. Just to correct you, my scene is still:

Player (KinematicBody2D)

  • States (Node)
    • Idle (Node)
    • Run (Node)
    • Jump (Node)

So my design is this -

  • Create all exportable motion variables on Player (KinematicBody2D) node by attaching a script Player.gd, so that when I instance this scene in the game scene, I don’t have to expand child scenes, but select this scene to tweak variable values
  • Create a script Motion.gd, not attached to any Node, as a base class for all motion related methods and variables. Access all variables in Player.gd and create method definitions to be accessed by child class

My script pattern looks like this:

Motion.gd

  • AerialMotion.gd
    • Jump.gd (attached to jump state node)
    • Fall.gd (attached to fall state node)
  • GroundMotion.gd
    • Idle.gd (attached to idle state node)
    • Run.gd (attached to run state node)

I tried the autoload to create a Singleton, but that doesn’t serve the purpose, cause if I change the exported variables’ value through the inspector, it still picks the values defined in the script.

utopiansocialist | 2020-04-30 17:31