The problem with .duplicate in the GUI

Godot Version

Godot 4.4.1

Question

Hello, I have a problem when I create a new display in the GUI using .duplicate, then I don’t copy everything, please help.

Code (function):

func add_unit_display(unit: CharacterBody3D) -> void:
	var new_element = $BG.duplicate(true) 
	add_child(new_element)
	new_element.get_child(0).editor_description = str(unit)
	new_element.top_level = true
	new_element.visible = true
	army_elements.append(new_element)
	unit.reparent($ARMS)

Error:

E 0:00:04:216   army.gd:81 @ add_unit_display(): Node not found: "Many/ScrollContainer/VBoxContainer/@Button@3" (relative to "BG").
  <Error C++>  Method/function failed. Returning: nullptr
  <Source code C++>scene/main/node.cpp:1877 @ get_node()
  <Stack tracing>army.gd:81 @ add_unit_display()
                army.gd:70 @ update_army_display()
                army.gd:47 @ _process()

What do you see if you call new_element.print_tree_pretty()?

1 Like

@ColorRect@5
┠╴Name
┠╴One
┃ ┠╴IconPer
┃ ┠╴Vibor
┃ ┖╴Stat
┠╴Many
┃ ┠╴ScrollContainer
┃ ┃ ┠╴VBoxContainer
┃ ┃ ┃ ┠╴Varm
┃ ┃ ┃ ┠╴ArmName
┃ ┃ ┃ ┠╴_Button_3
┃ ┃ ┃ ┖╴_Button_4
┃ ┃ ┠╴_h_scroll
┃ ┃ ┖╴_v_scroll
┃ ┖╴Stat
┖╴Clouse

The error is complaining about @Button@3, but you have _Button_3. It looks to me like the names are getting mangled.

1 Like

Do you know how to fix it? :pleading_face::pleading_face::pleading_face:

I don’t know if there’s a more systematic way, but there’s always brute force:

func add_unit_display(unit: CharacterBody3D) -> void:
	var new_element = $BG.duplicate(true) 
    new_element.get_node("ScrollContainer/VBoxContainer/_Button_3").name = "Button3"
    new_element.get_node("ScrollContainer/VBoxContainer/_Button_4").name = "Button4"
    [...]

I won’t be able to test this method today, but it looks interesting. I’ll try it tomorrow. Thank

Hello, I tried this method after the “var new_element = $BG.duplicate(true)” line. Although “new_element.print_tree_pretty()” shows what it’s supposed to show, the error still exists, and it’s specifically in the line with “var new_element = $BG.duplicate(true)”.

That implies _Button_3 is not a valid instance somehow. Is the thing you’re duplicating maybe not fully created yet? As in, some of its children are not yet created?

Idk. I’ll try to do something about it, but I don’t have any ideas.

Maybe before calling duplicate, recursively walk over the thing you’re duplicating and call is_valid_instance() on all the children? It would only be a debug/testing thing, but if any of those calls came back false it would be diagnostic.

In general, I’ve been thinking a lot about how to fix the error, but I don’t understand anything. Can someone figure out the code and help me?

All script code:

extends Control

var army_elements := []  
var close_requested := false  
var on_for = false

func _process(delta: float) -> void:
	if Global.army.size() == 1:
		$BG/One.visible = true
		$BG/Many.visible = false
		$BG/Name.text = str(Global.army[0].editor_description)

	if Global.army.size() > 1 and !on_for:
		on_for = true
		$BG/Name.text = "Армии"
		$BG/One.visible = false
		$BG/Many.visible = true
		for i in Global.army.size():
			var container = $BG/Many/ScrollContainer/VBoxContainer
			for child in container.get_children():
				if child.name != "ArmName" and child.name != "Varm":  
					child.queue_free()
			var new_element = $BG/Many/ScrollContainer/VBoxContainer/ArmName.duplicate()  
			$BG/Many/ScrollContainer/VBoxContainer.add_child(new_element)
			new_element.text = Global.army[i].editor_description
			new_element.visible = true
			var count = 0
			for child in container.get_children():
				if new_element.text == Global.army[i].editor_description:
					count += 1
				if count >= 2 and child.name != "ArmName" and child.name != "Varm":
					child.queue_free()
		on_for = false




	
	army_elements = army_elements.filter(func(element): 
		return element != null and !str(element).begins_with("<Freed Object>")
	)
	
	if close_requested:
		close_requested = false
		handle_close_operation()  
	else:
		update_army_display()  


func handle_close_operation() -> void:
	
	for i in range(army_elements.size() - 1, -1, -1):
		var element = army_elements[i]
		var child = element.get_child(0) if element else null
		
		
		for unit in $ARMS.get_children():
			if unit is CharacterBody3D and child and str(unit) == child.editor_description:
				if Global.army.has(unit):
					Global.army.erase(unit)
					unit.reparent(self)
					element.queue_free()
					army_elements.remove_at(i)
					break


func update_army_display() -> void:
	for unit in Global.army:
		if !is_unit_displayed(unit):
			add_unit_display(unit)


func is_unit_displayed(unit: CharacterBody3D) -> bool:
	for element in army_elements:
		if element and element.get_child(0).editor_description == str(unit):
			return true
	return false


func add_unit_display(unit: CharacterBody3D) -> void:
	var new_element = $BG.duplicate(true)
	new_element.print_tree_pretty()
	if Global.army.size() >= 2:
		new_element.get_node("Many/ScrollContainer/VBoxContainer/_Label_3").name = "Label@3"
		new_element.get_node("Many/ScrollContainer/VBoxContainer/_Label_4").name = "Label@4"
	add_child(new_element)
	#get_child(2).get_child(0).get_child(0).get_child(2).get_child(0)
	new_element.get_child(0).editor_description = str(unit)
	new_element.top_level = true
	new_element.visible = true
	army_elements.append(new_element)
	unit.reparent($ARMS)
	new_element.print_tree_pretty()


func _on_clouse_pressed() -> void:
	close_requested = true


func _on_ubr_pressed() -> void:
	print(123)

Why are you doing things with editor_description?

What is on_for supposed to do?

Why are you calling duplicate(true)? duplicate() takes an integer flags argument to tell which parts to clone, and by default is 15 (that is, binary 1111, all flags set). I’m not sure what true turns into when cast this way, but if it isn’t basically all bits set then you’re turning off some of the duplication.

2 Likes

duplicate (true) I specify it to be copied better (that’s what it says on the Internet), and on_for prevents new loops from being created forever, in _procces

editor_description я использую как хранилище для имени (не ноды, а просто имени)

Copied… better? I’m not sure I understand. duplicate() takes an integer argument, with the default argument being 15. Each bit in that (there are four, all set in 15) controls what is and isn’t duplicated:

DuplicateFlags DUPLICATE_SIGNALS = 1
    Duplicate the node's signal connections.

DuplicateFlags DUPLICATE_GROUPS = 2
    Duplicate the node's groups.

DuplicateFlags DUPLICATE_SCRIPTS = 4
    Duplicate the node's script (also overriding the duplicated children's scripts, if combined with DUPLICATE_USE_INSTANTIATION).

DuplicateFlags DUPLICATE_USE_INSTANTIATION = 8
    Duplicate using PackedScene.instantiate(). If the node comes from a scene saved on disk, reuses PackedScene.instantiate() as the base for the duplicated node and its children.

That’s from the Node docs. I don’t see why you wouldn’t want all of those set, and calling with true may not do that.

With on_for, your code looks like this:

var on_for = false

func _process(delta: float) -> void:
    [...]
    if Global.army_size() > 1 and !on_for:
        on_for = true
        [stuff]
        on_for = false

I don’t see how that’s going to do anything useful. Outside of that if block, on_for appears to always be false. It’s a local var, so it’s not going to affect other nodes.

Apologies if google translate made a hash of your comment about editor_description:

“I use it as a storage for the name (not the node, just the name)”

I’d advise against this; I’m not sure if a field like that would be stripped out in release builds…

2 Likes

I didn’t quite understand what you meant by (on_for), but without it, the code doesn’t work correctly.

And in the code, neither dublicate or duplicate(true) change anything. The documentation turns out to be very hard to read since it either doesn’t fully translate or translates incorrectly.

DeepSeek helps me with these questions (ducumentation).

Example for Editor_description:

editor_description = "Army of liberty"
or
editor_description = "My Army"

What I mean about on_for is, I don’t see what it could possibly be doing. It will always be false when it gets tested, so the !on_for test will always evaluate to true.

When the for loop starts, on_for does not allow it to start indefinitely.

Which loop? The for i in Global.army.size() one? I don’t see how one would affect the other.

1 Like

duplicate takes a bitflag as argument, that’s what hexgrid was trying to tell you, by setting it to true, you’re stripping it from some of the default duplication flags.
Assuming true == 1, that means it would just duplicate the Node’s signals.
See Node — Godot Engine (stable) documentation in English
Hope this makes it clearer,
Cheers !

1 Like