Attempting Modularity with Node method (and failing)

Godot Version

v4.2.2.stable.flathub [15073afe3]

Question

I’ve been working with a marching cubes script, and I’ve come across the situation where I want to keep the script unchanged, but incorporate some modularity with the noise generation. That’s all fine and dandy, but I’ve come across several roadblocks.

  1. Noise is a native class, and thus can’t be overwritten (subclassing goes out the window)
  2. Resources don’t support functions (I’ve tried this before I read the docs more thoroughly and realized it’s designed that way)
  3. Having a node in the scene, and calling the node’s function either has to be static, or refuses to work

More on #3, which I believe may be holding a critical misunderstanding I have.

@tool
extends MeshInstance3D
...
@export var NOISE_PATH: NodePath
...

var NOISE_NODE
var NOISE: NoiseGen
@export var GENERATE: bool:
	set(value):
		var time = Time.get_ticks_msec()
		NOISE_NODE = get_node_or_null(NOISE_PATH)
		if NOISE_NODE != null:
			NOISE = NOISE_NODE as NoiseGen
			print(NOISE.has_method("get_noise_3d"))
			print(NOISE.get_script().get_noise_3d(0,0,0))
			NOISE.get_noise_3d(0,0,0)
			generate()
...

This gets me the following output consistently

 Can't call non-static function 'get_noise_3d' in script.
  res://Scripts/Terrain.gd:21 - Invalid call. Nonexistent function 'get_noise_3d' in base 'Node (NoiseGen)'.
Set GENERATE
true

Here’s some info and the relevant files
MeshInstance3D7 contains the editor tool which I’m trying to run.
image

NoiseGen.gd

@tool
class_name NoiseGen
extends Node

@export var noise: FastNoiseLite
@export var noise_strength:= 0.1

func get_noise_3d(x, y, z) -> float:
	return Vector3(x,y,z).length() - 10 + noise.get_noise_3d(x,y,z);

Truncated Terrain.gd

@tool
extends MeshInstance3D
@export var NOISE_PATH: NodePath

var NOISE_NODE
var NOISE: NoiseGen
@export var GENERATE: bool:
	set(value):
		var time = Time.get_ticks_msec()
		NOISE_NODE = get_node_or_null(NOISE_PATH)
		if NOISE_NODE != null:
			NOISE = NOISE_NODE as NoiseGen
			print(NOISE.has_method("get_noise_3d"))
			print(NOISE.get_script().get_noise_3d(0,0,0))
			NOISE.get_noise_3d(0,0,0)
		else:
			push_error("No Noise Gen Selected")
		var elapsed = (Time.get_ticks_msec()-time)/1000.0
		print("Terrain generated in: " + str(elapsed) + "s")

? I’m not sure that is true. But maybe you may mean a different type.

I’m confused again. There is a place for statics and a place for references and autoloads. So it depends on what is being called and how you call it.

You should test that NOISE is not null after this.

You could just export like this

@export var NOISE_GEN: NoiseGen

You should test if noise is valid before interacting with it.

1 Like

Your error is strange it doesn’t make sense to me, but your code looks problematic. I wonder if you assigned a script to the export variable for NOISE. Which would succeed in the conversation, but act like a static reference.

Since these are also @tools there could be problems the editor. You could try closing the tab to the scenes that contain the tools to the reopen them and see if that clears the issue.

So i’ve been doing some tests, this has been getting weirder by the second.
I’ve gotten the following results, with using some print statements (3.14 here’s used to make sure no weird casting bugs going on)

isnull : false
class : Node
cast : NoiseGen:<Node#13212628095520>
cast class : Node
getnode : NoiseGen:<Node#13212628095520>
getnode cast test : 3.14
getnode test : 3.14
Terrain generated in: 0s

Here’s the print code that generated the following (misc omitted)
The lines commented out in generate produce errors, for unexplained reasons,
yet most confusingly fetching the node via getNode (essentially doing nothing) will produce the correct results?!?

@tool
class_name VoxelVolume
extends Node3D

@export var TEST_NOISE: NoiseGen
@export var GENERATE: bool:
	set(value):
		var time = Time.get_ticks_msec()
		generate()
		var elapsed = (Time.get_ticks_msec()-time)/1000.0
		print("Terrain generated in: " + str(elapsed) + "s")
		return false
	get:
		return false

func generate():
	print("isnull : ", TEST_NOISE == null)
	print("class : ", TEST_NOISE.get_class())
	# bugged?
	# print("test : ", TEST_NOISE.get_noise_3d(0,0,0))
	print("cast : ", TEST_NOISE as NoiseGen)
	print("cast class : ", (TEST_NOISE as NoiseGen).get_class())
	# bugged?
	# print("test noise : ", (TEST_NOISE as NoiseGen).get_noise_3d(0,0,0))
	print("getnode : ", get_node(TEST_NOISE.get_path()))
	print("getnode cast test : ", (get_node(TEST_NOISE.get_path()) as NoiseGen).get_noise_3d(0,0,0))
	print("getnode test : ", get_node(TEST_NOISE.get_path()).get_noise_3d(0,0,0))
	return

And here’s NoiseGen.gd

@tool
class_name NoiseGen
extends Node

func get_noise_3d(x, y, z) -> float:
	return 3.14;

I forgot to mention, when creating an instance of NoiseGen using new() in the script itself, everything functions as it should. Is there an internal difference between the

@export var TEST_NOISE: NoiseGen

and the

NoiseGen.new()

when the references are created, other than the new object’s reference count is only this script?

As a sanity check, I re-ran the tests that were failing and realized my mistake.
Due to using node_path, it likely fetched only the Node instead of the NoiseGen component, which is why casting failed at the place that it did.

The correct way to fetch stuff like this would be as you suggested, and somewhat magically the errors go away.

Interesting…

One thing that you said cought my attention was the use of NoiseGen.new(). It is useful for script only class that doesn’t have any children nodes like a .tscn scene.

If you have a scene .tscn with an extended class and class_name with exported saved references, using new() will ignore all of that and just give you the bare extended class with it’s default values as a single node.

This could maybe explain the static reference? It would definitely have left the fastnoise uninitialized, because it was as an exported value set in a .tscn, when it was created with new()