Hello!
Im making a 3D game and trying to make an animation for enemies when they receive a hit.
This is my Enemy "Skull" scene setup

The animation changes the position of the Body mesh (not the entire enemy scene) so it "vibrates"
The animation also makes the albedo color of the mesh material switch back and forth from a red color so it "blinks".

The problem is when i attack a Skull the animations plays normally but all other Skulls also change color (but not position).

My guess is all Skulls share the same mesh instance, so changing its property will affect all Skull instances.
What could be a good fix for this problem? Should i make all instances of the mesh unique? Can i do that from code? Is there a better approach?

  • xyz replied to this.
  • "local to scene" flag only works for scenes instantiated at runtime via PackedScene::instantiate(). It won't work for instances manually placed in the editor. So for each such instance you need to either make the resource unique by gui-clicking, or duplicate the resource from a script at startup as z80 suggested. Only that I'd do it from within the scene itself.

    However, this is not very smart for a setup like yours since you have the material assigned directly to the mesh resource. This means you'd need to duplicate the mesh data too which is a massive overkill and defies the purpose of using the mesh instance node in the first place.

    But. As this is a very common problem, the MeshInstance3D node has material overrides which do respect "local to scene" flags for assigned material resources. So instead of assigning the material directly to the mesh, assign it either to:

    • surface overrides if you have multiple materials per mesh
    • or to global material override if you only have a single material

    Now the assigned material(s) will be duplicated per manually placed instance if material's "local to scene" flags is set to true. It will, of course, work for runtime instances too.

    Cabeza
    If I had a penny for every time I said this... 😃

    Enable material resource's local_to_scene flag. That way, every time the scene is instantiated, the resource will be duplicated, making it unique for that instance. Otherwise all instances will hold a reference to the same resource object, so changing it via one instance will apparently change it in every other too.

    Don't feel too bad. I just learned that flag existed. I've right clicked the material inside the mesh and hit "make unique" for every single mesh in a scene up until now.
    It's a solution, but @xyz's is better in most cases.

      packrat i didn't know about that flag. I've enabled it but the problem persists.

      The mesh resource is now local:

      And so is the material on the mesh:

      I removed the previous enemies from the level scene and re-added them just in case

      I solved it! But im not quite sure how .
      I kept making the reosurces unique and seting "local_to_scene" to true. I re-animated the color proerty and now it works!

      What happens if you change the albedo color of one instance directly via code, without using animation?

        xyz The problem returned! I made a level script to change the color of a single mob:

        All instances of the mob change their colors!

        @Cabeza
        If healthy state of mind is your life priority, forget about local_to_scene flag and the place where it is located! Its only purpose it to make game developers go bananas!

        The method I've found guaranteed to work all the time is to manually call duplicate() the material (or any other resource type) I want to tweak individually and dynamically re-assign it to all instances. This method always works. It's like this:

        var m: ShaderMaterial = my_material.duplicate() # This is the key line(!!!!!!!!!!)
        
        m.set_shader_parameter( "parameter_a", value_a )
        m.set_shader_parameter( "parameter_b", value_b )
        mesh_instance.material_override = m

        What I'd do is I'd make a export with a material I wanted to use and assigned it via calling duplicate() in _ _ready() _ method. Then I'd have completely independent material instances in all the meshes.

        @export material var my_material: ShaderMaterial = preload( "res://my_material.tres" )
        
        func _ready():
            var m: ShaderMaterial = my_material.duplicate()
            material_override = m # Here it is material override just for illustration. You can assign it to surface_material or any other place.

        If you do this, you'll only rarely recall local_to_scene flag in nightmares...

        • xyz replied to this.

          z80 forget about local_to_scene flag and the place where it is located! Its only purpose it to make game developers go bananas!

          Only if they don't understand what it's doing 😉

          @Cabeza Can you post an example project that reproduces the problem? I'd like to take a look.

            xyz
            You can find a sample scene in src/levels/test.tscn

            sample.rar
            26MB

            The mob scene is in src/mobs/skull.tscn

            The game has standard FPS controls (WASD to move, mouse to look and left click to shoot).
            Shooting Skulls triggers their animations and color change

            xyz Only if they don't understand what it's doing 😉

            Yes, true, its tooltip says "If True, the resource is duplicated for each instance of all scenes using it. At run-time, the resource can be modified in one scene without affecting other instances." How I understand what is written is that if local_to_scene is checked, different instances shouldn't affect each other. But it is not always the case in reality for some reason.

            • xyz replied to this.

              "local to scene" flag only works for scenes instantiated at runtime via PackedScene::instantiate(). It won't work for instances manually placed in the editor. So for each such instance you need to either make the resource unique by gui-clicking, or duplicate the resource from a script at startup as z80 suggested. Only that I'd do it from within the scene itself.

              However, this is not very smart for a setup like yours since you have the material assigned directly to the mesh resource. This means you'd need to duplicate the mesh data too which is a massive overkill and defies the purpose of using the mesh instance node in the first place.

              But. As this is a very common problem, the MeshInstance3D node has material overrides which do respect "local to scene" flags for assigned material resources. So instead of assigning the material directly to the mesh, assign it either to:

              • surface overrides if you have multiple materials per mesh
              • or to global material override if you only have a single material

              Now the assigned material(s) will be duplicated per manually placed instance if material's "local to scene" flags is set to true. It will, of course, work for runtime instances too.

                xyz It won't work for instances manually placed in the editor.

                I've just tested this exact case. At least in a toy scene in 4.0.2 it works 🙂 And, based on your comments, it looks like in the real project it doesn't?

                local-to-scene-test.zip
                4MB

                That's basically what I referred to when suggested to switch to using duplicate() and forged the problem with local_to_scene as a nightmare 🙂

                xyz you'd need to duplicate the mesh data too which is a massive overkill and defies the purpose of using the mesh instance node

                It is possible to get_mesh().surface_set_material( 0, get_mesh().surface_get_material( 0 ).duplicate() ), i.e. duplicate just the material?

                • xyz replied to this.

                  z80 I've just tested this exact case. At least in a toy scene in 4.0.2 it works 🙂 And, based on your comments, it looks like in the real project it doesn't?

                  Of course it works because you assigned the material to surface material override. It wouldn't work had you just assigned it directly to the mesh resource. Note the distinction. This was my point exactly.

                  z80 It is possible to get_mesh().surface_set_material( 0, get_mesh().surface_get_material( 0 ).duplicate() ), i.e. duplicate just the material?

                  Of course it's possible. It won't solve the problem though. Think about it. If multiple nodes each hold a reference to the same mesh resource and you swap the material on that mesh resource, all of the nodes will still refer to the same mesh which will just have a new material instead of the old one.

                  The way I described it is the "proper" way to do it. Use automatic resource duplication provided to you by the engine via "local to scene" and material override mechanisms. Material overrides are there for a reason, and the reason is precisely the ability to separately manipulate material properties on each instance of the same mesh data.

                  • z80 replied to this.

                    xyz Think about it. If multiple nodes each hold a reference to the same mesh resource and you swap the material on that mesh resource, all of the nodes will still refer to the same mesh which will just have a new material instead of the old one.

                    Yes, after you've explained it in detail, it all makes sense now 🙂 Thank you so much!!! I don't really know why, but I expected nodes to parse resources, make copies of everything labeled "local_to_scene=true", and dynamically apply when rendering the scene. Now (after you've explained it) I realize that instead of doing that they made material override. Thank you!

                    • xyz replied to this.

                      z80 don't really know why, but I expected nodes to parse resources, make copies of everything labeled "local_to_scene=true", and dynamically apply when rendering the scene

                      That's not an unreasonable assumption. Tbh, off the top of my head, I can't really see a reason not to implement it like that, but I guess the devs had some legitimate reasons.

                      Have in mind that things still do happen exactly in the way you described (hunting down all resources within the scene and duplicating them) but only for runtime instantiation.

                      However, even if editor instances behaved like this, using material overrides would still be preferable to avoid needless copying of vertex data, which could potentially lead to storage and performance problems.

                      The meshes own material slot is there to act as the default fallback and overrides are what you should use to make the instances unique. Seems all very sensible to me. Imagine you're the person in the studio in charge of the asset pipeline, you (and any in your department) dealing with asset management import and set up the defaults on the imported assets.

                      You set up the mesh with a default fallback material. A person in the game/level design dep. throws some assets around a level as placeholders for testing, those assets now have materials on them as the fallback even though they didn't bother to set up any overrides on them.

                      Alternatively lets imagine a situation where on some players specific system for whatever ungodly reason a shader/material fails to compile properly(maybe their gpu claims support for feature-set that is not fully implemented or the specific driver version has a bug in it) and the override doesn't apply. At least the asset in the published game will still render with the simpler fallback material. Redundancy is never truly redundant.

                      @xyz @Megalomaniak @z80
                      Thank you all for your input! Im still fairly new to the engine so these types of answers are incredibly useful to me.

                      xyz
                      This insight is specially useful. Thank you for your time!