Change a material's colour

Hi, I have a UI and would like a button that changes the colour of a specified material and applies this to for all objects that use it (rather than choose an object and change the colour. Is that possible?
I’ve seen Switching materials at runtime | Learn PlayCanvas but it’s not quite what I’m after. Unfortunately I have objects with multiple components that all have different names but they all use the same material so it would save me a lot of time / hassle if this is possible.
Appreciate your help.

1 Like

Hi @Lenster and welcome!

I think it has to be something like below, but I’m not sure.

var mesh = this.entity.render.meshInstances[0];
mesh.material.diffuse = new pc.Color(1,1,1);
mesh.material.update();

If the code is correct, it will change the color of all objects with the same material.

1 Like

Thanks @Albertos Ideally I’d like the button to choose the material named yellowMaterial then change it on all objects that is using that material. Is that possible, as the following just throws an error

var mat = this.app.root.findByName('yellowMaterial');
var mesh = mat.render.meshInstances[0];
mesh.material.diffuse = new pc.Color(1,1,1);
mesh.material.update();

Cannot read properties of null (reading ‘render’)

and when I just select the entity

var mat = this.app.root.findByName('yellowObject');

it states

Cannot read properties of undefined (reading ‘meshInstances’)

1 Like

Do you use the render component on your entity or the old model component? Make sure there is a render component on the entity or replace render with model in the code.

If you want to use material from the scene it will be different and I’m not sure how to do this off the top of my head. Changing the material means that you need to change the material on all other entities as well, so you need to have a way to find all entities in scene.

1 Like

Course, the entity is a template instance that contains multiple entities (with render components) :man_facepalming: Each of those entities contained within have the render component, but all of these entities have different names. So for the entities that are using the yellow I’d like to change all of them to a different colour when I click a button but so far the only other way I can think of doing it is to declare every entity that needs changing but that would be an arduous task so was hoping I could just change the material, or all those sub-entities in the instance that use the material and only have to declare the root entity/instance. Hope that makes sense :grimacing:

1 Like

Yes, you can change the color of the material and it will also change the color on all other entities with the same material. For this you need to have access to the material. You can get it from an entity like my first post or maybe you can use an attribute and attach the material to the attribute (like what is done in the example project you shared).

Only if you want to replace the material with another material, you need to replace the material on the other entities as well.

1 Like

I feel like I still misunderstand what you try to do. If the problem is only the different names of the entities, maybe you can give the entities a specific tag, like yellow. Then you can use findByTag() to get an array of those entities, to be able to replace the material or something.

1 Like

I’m probably not explaining it clearly. I’ve quickly created this as a demo:
https://playcanvas.com/project/1179402

The template instance is ‘playcanvas’. Within there it has two entities ‘canvas’ and ‘play’. For both of these I’ve changed the material to yellow.

I’ve created a button. When I click the button I would like to change all entities (‘canvas’ and ‘play’) that use the yellow material to change to another colour.

I’ve been playing with this and I think I’ve sorted it. I’ll go with the findByTag and tag all those entities that use that colour - is there a more efficient way to do this as this might take me a while to tag every entity. If not, I’ll go with the following as it works!

var mat = this.app.root.findByTag('yellowMat');
var newMaterial = this.colourOptions[0].resource;  
this.entity.button.on('click', function(event) {
     mat.forEach(function(mat) {
         var mesh = mat.render.meshInstances[0];
         mesh.material = newMaterial;
         mesh.material.update();
     })
}, this);  

Finding a way around this has been frustrating so really appreciate your help @Albertos thanks :sunglasses:

2 Likes

You don’t have access to one of the materials you want to change the color of?

I’m not sure if there is a way to find all entities with a specific material. Maybe you can use this.app.root.findComponents('render') and loop through all mesh instances or something like that.

https://developer.playcanvas.com/en/api/pc.Entity.html#findComponents

1 Like

OK, so I can do this as below, but it changes every component to that material. Can I only change those with the yellow material

this.entity.button.on('click', function(event) {
      var temp = this.app.root.findComponents('render');
      temp.forEach(function(temp) {
            var mesh = temp.meshInstances[0];
            mesh.material = newMaterial;
            mesh.material.update();
      })
}, this);  

Although, if I add this it appears to work and conveniently doesn’t change the material name so I can use the same method to change it again if required

this.entity.button.on('click', function(event) {
      var temp = this.app.root.findComponents('render');
      temp.forEach(function(temp) {
            if (temp.material.name == "yellowMaterial") {
                  var mesh = temp.meshInstances[0];
                  mesh.material = newMaterial;
                  mesh.material.update();
            }
      })
}, this);  

I’m conscious of anything that might also impact speed/efficiency but this could be ideal, does that look like a decent solution?

1 Like

Yes, something like that is what I had in mind.

Curious why the name of the material is not changed if you have changed the material.

1 Like

Not sure. I just tried to view the object before and after that function using:

console.log("my object: %o", temp);

and the material name didn’t seem to change :man_shrugging:

I will probably allow the material to be changed more than once so will test and let you know if it does change the name. If so, I will have to change the IF condition, I’m going to use a script attribute to add the materials so will use the material.name if the array includes it.
Thanks again @Albertos

2 Likes