I'm working on a HUD for a visual novel. I have a cache for characters that have been shown so far (independent of the characters actually on-screen), a function to hide them and another one to show them, move them around, change expressions, etc.

func show_char(who: String, model: String, lock_chars := true) -> void:
	assert(who != "")
	assert(who.is_valid_filename(), "'%s'" % who)

	# Default to base_model if called with an empty string
	if model == "":
		model = base_model
	assert(model.is_valid_filename(), "'%s'" % model)

	# Determine side
	var side: Container
	if _next_side:
		side = %Characters/Right
	else:
		side = %Characters/Left
	assert(side != null)

	# If this character has been shown before, simply reuse the node
	var x: AspectRatioContainer = _chars.get(who)
	if x:
		print(x.get_node("model"))
		var model = char_path + who + "/" + model
		x.get_node("%model").texture = load(model)
		# If the character is shown on the correct side or lock_chars
		# is true, do nothing
		if x.get_parent() == side or lock_chars:
			return
		# If the character is shown on a different side, hide them
		if x.get_parent() != null:
			x.get_parent().remove_child(x)
		# Show character on the correct side
		side.add_child(x)
		x.set_owner(side)
		# Toggle side
		_next_side = not _next_side
		return

	# Character not shown before. Build a new branch
	assert(_chars.get(who) == null, 
			"Tried to create an existing character '%s'" % who)
	# Prepare the branch
	var img := StyleBoxTexture.new()
	var img_panel := Panel.new()
	var img_container := AspectRatioContainer.new()
	# Load texture
	model = char_path + who + "/" + model
	assert(ResourceLoader.exists(model),"%s" % model)
	img.texture = load(model)
	# Adjust min size
	img_container.set_custom_minimum_size(img.texture.get_size())
	# Build the new panel
	img_panel.add_theme_stylebox_override("panel", img)
	img_panel.set_name("model")
	img_panel.set_unique_name_in_owner(true)
	# Add panel to container
	img_container.add_child(img_panel)
	img_panel.set_owner(img_container)
	# Name container
	img_container.set_name(who)
	img_container.set_unique_name_in_owner(true)
	# Record it in _chars
	_chars[who] = img_container
	# Add it to SceneTree
	side.add_child(img_container)
	img_container.set_owner(side)
	# Toggle side
	_next_side = not _next_side


func hide_char(who: String) -> void:
	assert(_chars.get(who) != null, "%s" % who)
	assert(_chars.get(who).get_parent() != null, "%s" % who)

	# Search CharacterRow for the given character and hide them.
	var x: AspectRatioContainer = _chars.get(who)
	x.get_parent().remove_child(x)
	x.set_owner(null)

The character row is just a bunch of HBoxContainers inside of a MarginContainer.

The problem comes from hiding and then showing the same character model. The first call to load(), in the 2nd part of show_char(), returns a Texture2D, and life is good. The 2nd call then tries to load the same image but returns a CompressedTexture2D, which raises an Invalid set index error. How can I force load() to always return a Texture2D?

  • TwoFox Well the error message says it all. Panel node doesn't have the texture property so you can't assign to it.

    If you want to change the texture on the Panel's stylebox that you previously overriden via Panel::add_theme_stylebox_override(), you'll need to retrieve Panel's stylebox object using Panel::get_theme_stylebox(), and assign the texture to that object.

    To put it more simply you're trying to do this:

    Panel.texture = some_texture

    While you need to do this:

    Panel.stylebox.texture = some_texture

    But you cannot do it via dot syntax like that (for reasons we won't get into now). Instead you need to call Panel::get_theme_stylebox() to get the reference to the stylebox object.

    So instead of:

    x.get_node("%model").texture = load(model)

    You'll have to do:

    x.get_node("%model").get_theme_stylebox("panel").texture = load(mode)

    Assuming that "panel" is the name of an existing stylebox override you assigned earlier.

Ivalid set index is typically raised when you try to assign to a non-existent property.

Here you assign to an existing property of the StyleBoxTexture object:

var img = StyleBoxTexture.new()
img.texture = load(model)

But here you're assigning to a node that might not have the texture property:

x.get_node("%model").texture = load(model)

    xyz That should be impossible. Every character that gets created is added to _chars. And that part of the function is called only if _chars.get() returns non-null. Also, the full error says:

    Invalid set index 'texture' (on base: 'Panel') with value of type 'CompressedTexture2D'

    even if I use x.get_child(0), instead of x.get_node(), so it looks to me like the game can find the right node.

    • xyz replied to this.

      TwoFox Well the error message says it all. Panel node doesn't have the texture property so you can't assign to it.

      If you want to change the texture on the Panel's stylebox that you previously overriden via Panel::add_theme_stylebox_override(), you'll need to retrieve Panel's stylebox object using Panel::get_theme_stylebox(), and assign the texture to that object.

      To put it more simply you're trying to do this:

      Panel.texture = some_texture

      While you need to do this:

      Panel.stylebox.texture = some_texture

      But you cannot do it via dot syntax like that (for reasons we won't get into now). Instead you need to call Panel::get_theme_stylebox() to get the reference to the stylebox object.

      So instead of:

      x.get_node("%model").texture = load(model)

      You'll have to do:

      x.get_node("%model").get_theme_stylebox("panel").texture = load(mode)

      Assuming that "panel" is the name of an existing stylebox override you assigned earlier.

        xyz I completely forgot about styleboxes and overrides. Thank you