Running script for multiple mesh nodes

Godot Version

v4.3.beta2

Question

I need to change the standard material properties during runtine. After digging through documentation, I’ve come up with this simple solution:

extends MeshInstance3D

var material_overrides:Dictionary = {
	"metal_plate": {"metallic": 1.0},
	"wet_brick": {"roughness": 0.3},
}

func _ready() -> void:
	var surface_name:String = mesh.get("surface_0/name")
	apply_override(surface_name)

func apply_override(surface_name:String) -> void:
	if material_overrides.has(surface_name):
		var properties:Dictionary = material_overrides[surface_name]
		var material:Material = mesh.surface_get_material(0)

		for p:String in properties:
			material.set(p, properties[p])

So here is the problem: I need to run this code for every mesh in the scene under a specific node. Sample scene layout:

+Level
 +--Geometry // WE WANT TO UPDATE THESE MESHES
 |  +--Floor (StaticBody3D)
 |     +--MeshInstance3D
 |     +--CollisionShape3D
 |  +--Wall (StaticBody3D)
 |     +--MeshInstance3D
 |     +--CollisionShape3D
 +--Props // DON'T WANT TO TOUCH THESE
    +--Fence (StaticBody3D)
       +--MeshInstance3D
       +--CollisionShape3D
    +--Tree (StaticBody3D)
       +--MeshInstance3D
       +--CollisionShape3D

Any idea how to do this without manually attaching the script to every single mesh node (there will be a lot of them)?

I’m new to Godot and still learning the basics :pleading_face::point_right::point_left:

I think I’ve figured it out.
geometry.gd:

extends Node3D

var material_overrides:Resource = preload("res://scripts/material_overrides.gd")

func _ready() -> void:
	for x in get_child_count():
		for y in get_child(x).get_children():
			if y.get_class() == "MeshInstance3D":
				y.set_script(material_overrides)

material_overrides.gd:

extends MeshInstance3D

var material_overrides:Dictionary = {
	"metal_plate": {"metallic": 1.0},
	"wet_brick": {"roughness": 0.3},
}

func _init() -> void:
	var surface_name:String = mesh.get("surface_0/name")
	apply_override(surface_name)

func apply_override(surface_name:String) -> void:
	if material_overrides.has(surface_name):
		var properties:Dictionary = material_overrides[surface_name]
		var material:Material = mesh.surface_get_material(0)

		for p:String in properties:
			material.set(p, properties[p])

Don’t really like 3 nested loops, but hey, at least this code is only run once when the tree is ready, so it should not be that big of a deal.
But anyway, is there any better way to reference a specific child node by it’s type?

This feels a bit unintuitive, but it’s hard to point out why since I’m not sure why you’re changing the values/what you are trying to achieve.

But generally I would advise against using set_script. There are much cleaner solutions.

You could use Godot’s Group system to get the relevant nodes and then modify them from one script. I would also prefer to create a completely new material with the options you want to use e.g. wet_brick_material.tres

const wet_brick_mat := preload("res://web_brick_material.tres")

func change_materials():
    var nodes := get_tree().get_nodes_in_group("change_material")
    for node in nodes:
        node.material = wet_brick_mat 

If you don’t want to explicitly Group them for material change then your way of getting the node references if fine, though it could be made more “general”. Choose a starting node and then recursively fetch it’s children, add the meshes to an array, then fetch the children of those and add the meshes, etc. until there are no more children

The nested loops are necessary if you want to recursively look for children :slight_smile: It’s not a problem. Avoiding nested loops is a good principle but they are also often necessary, it’s just that they should be avoided if and when a better solution also exists

I’d suggest reading: Godot 4.0 gets global and per-instance shader uniforms for a broader solution