#Trouble with setting stencil_reference from script

26 messages · Page 1 of 1 (latest)

median ice
#

I'm trying to set up a card game system, where each card uses a separate stencil buffer reference to have its own "window" effect. I'm trying to accomplish this in the _ready function, by giving each card an ID, and using that ID as the stencil_reference for the card and the objects in the stencil buffer.

Notably, when I run the following code on multiple cards, each card updates ALL of the other cards to its stencil_reference. When I print the stencil_reference in the ready function, it seems to work, but then if I print it again later, everything is set to the last card's ID.

I'm not sure how to stop it from doing this. I've tried using set_local_to_scene(true) but that doesn't seem to be it. Any help is appreciated!

Code excerpt below:

@export var card_id: int

func _ready() -> void:
  $CardObject.get_mesh().surface_get_material(1).set_local_to_scene(true)
  $CardObject.get_mesh().surface_get_material(1).stencil_reference = card_id

  $CardInnerObject.get_mesh().surface_get_material(0).set_local_to_scene(true)
  $CardInnerObject.get_mesh().surface_get_material(0).stencil_reference = card_id

  print($CardObject.get_mesh().surface_get_material(1).stencil_reference, $CardInnerObject.get_mesh().surface_get_material(0).stencil_reference)
  await get_tree().create_timer(1.0).timeout
  print($CardObject.get_mesh().surface_get_material(1).stencil_reference, $CardInnerObject.get_mesh().surface_get_material(0).stencil_reference)
tiny wagon
#

Ever heard of duplicate? Pattern: resource.duplicate() Usage: var resource_instance: Resource = custom_resource.duplicate()
Reason: It creates a copy of the existing resource that is a wholly separate instance from it, and will not receive any changes you make to the original or other copies.

#

So, if your case is how it looks to me, you would need to copy the materials that are the base materials you've set on each, and store a reference to those copies, making any/all local changes to the new material resource copies.

#

setup_local_to_scene can only work for the top-level resource, unless the subresources are also setup_local_to_scene (and I'm not 100% sure this always works for every resource type). Additionally, if the resource is not yet part of a scene when it's called, or it's a standalone resource, it shouldn't do anything useful. It most specifically does not create a full runtime copy, it just serializes changes to the resource within the scene itself. As in, those changes are like "offsets" stored in your scene file at that point. The trouble is, making changes in code to the resource can bypass this if done at the wrong time, such as before this is properly called. I think _ready() is too early, because its called right when a node is entering the tree, and that means if it's a child of another node in the scene, then the scene is not completely loaded. As in, the root node of the scene would not have been ready yet. This may be why it's not working for you. I use duplicate() to allow me to basically ignore issues exactly like this one.

#

Though, this should be less of a problem on nodes that are instanced after the frame the scene is loaded in, because the scene would already have been loaded by then.

#

I haven't tested for that, but it logically makes sense, assuming setup_local_to_scene() is doing exactly what it says it does. We can have a look at the .cpp to check...

median ice
#

So... something like this?

@export var card_id: int

func _ready() -> void:
  var card_material := load("res://tres/card/card_material.tres").duplicate()
  card_material.stencil_reference = card_id
  $CardObject.get_mesh().surface_set_material(1, card_material)
tiny wagon
#

Something like this example for a packed scene in my current project:
const PACKED_SCENE: PackedScene = preload("uid://don8b8e4u8qs0")

#

Though, you can just store the path string, itself and use it later for loading.

#

To get a UID, you can right-click in the file system dock on a file, and select that option.

median ice
#

I've been using exported variables to avoid file restructuring issues but I feel like that's not exactly what that's for, thank you for the info!

tiny wagon
#

Exports work just fine for that, too. Though, they are volatile in tool scripts. Be warned.

#

They are also mutable...so less safe from the action of future you or someone else.

median ice
#

Good to know, ty

#

Now to see if I can't get this code fixed.

tiny wagon
#

In your case, load can take a uid string, so you can do a const for that like const CARD_MATERIAL_UID: String = "uid://dbe3f5y2ex8" or whatever it generates for it.

#

I always do const with them. You can sort of argue for regular file path references that you can tell what the thing is, but with a uid, you definitely cannot, and might as well hang it on a const for code clarity. Though, const is just a really good pattern for anything like this, really.

median ice
#

So, I still haven't had any luck with this, although I've made some progress.

#

I think the main problem I'm running into is that the card is an imported object.

#

Right now I'm instanciating an inherited scene created from the imported card, but every time I try to change the card's material, it changes every card, even through I'm duplicating the packed card scene.

#

I've successfully gotten the non-imported objects to have unique stencil_references, but I can't seem to figure out how to make the imported cards unique.

median ice
#

Finally figured this out. In the inherited scene, the MeshInstance3D's Mesh attribute was set to a mesh that wasn't set to be unique. Once I clicked "Make Unique" on it, everything fell into place.

tiny wagon
# median ice Right now I'm instanciating an inherited scene created from the imported card, b...

What do you mean exactly by 'instantiating an inherited scene'? That raises my eyebrows. Instantiate is something you call on PackedScene, which is a resource type, but PackedScene doesn't have configurability to 'inherit' things you specify, per-se. It's job is just to be a packed bundle of the scene data, and provide you with a copy (a unique copy, in fact) of that scene. The scene doesn't really inherit, but the nodes and scripts inside it do. Maybe you meant something other than inherit... Anyway, the thing you did as a fix, "setting make unique on the mesh resource" is the same as what happens when you duplicate it in code. So, know that if you need to automate this in code, using duplicate (calling it on the resource, not the scene or anything else that is instantiated) will work.

median ice
#

Oh, let me see if I can retrace my steps. The card is an imported gltf. I needed to add nodes to the card for functionality. So I clicked on "Scene -> New Inherited Scene..." with my card selected. Then I added those nodes, and saved the scene as a packed scene. I added code to instantiate the packed Card scene into the main scene.

The issue was stemming from the fact that in the inherited scene I created, the card's mesh was not unique. So I think even though I was making the materials unique in the code, they were being applied to a non-unique mesh. Updating the any of the card's materials overwrote the materials on all of the meshes because of that, thus the problem.

#

But I've got that all sorted now.