Can't change material's color through code

hey guys

I can’t seem to find how to change a material’s color anywhere (using Material.Color isn’t an option).

using Unity 2022.3.10f1

Edit:
Fixed and solved. Answer in the messages.

Original Post on Discord

by user 198447544408342528

Hi, do you have a script to show how you currently try doing it? For what are you trying to change the color? A 3D Object or a UI element?

a 3d object (mesh).

by user 198447544408342528

import { EventList, serializable } from "@needle-tools/engine"
import { IInteractable } from "./IInteractable";
import { Color, Material } from "three";

export class InteractableObject extends IInteractable{

    /** The event that will occur opun interaction */
    @serializable(EventList) onInteraction?: EventList;
    /** The material to set the color of interactable object. Leave undefined to not apply material change */
    @serializable() materialToSetColor?: Material;
    /** The color of the interactable object. */
    @serializable() colorOfInteractable?: Color;

    override Interact(): () => void {
        return () => this.onInteraction?.invoke();
    }

    // coloring the object to show its interactable
    start(): void {
        if (this.materialToSetColor == undefined) return;

        if (!this.materialToSetColor) return;
        this.materialToSetColor.Color = colorOfInteractable;
    }
}

by user 198447544408342528

the last line " this.materialToSetColor.Color = colorOfInteractable; " has an error ā€œProperty ā€˜Color’ does not exist on type ā€˜Material’.ts(2339)ā€

by user 198447544408342528

Ah yes that’s the way threejs works. They have different material types that represent their shaders - so different Material types have different properties. You can change the type to e.g. MeshStandardMaterial

Btw the lines with Material and Color should also get the type: @serializable(Color)

it’s also .color (lowercase :slightly_smiling_face: )

See MeshStandardMaterial.color

The Material class doesnt have a color property

could you please explain more about when I need to put a variable in the serializeable ā€œattributeā€?

by user 198447544408342528

It’s for deserialization of the data stored in the GLB. When your GLB exported from Unity or Blender is loaded and the scripts are created we have to re-create the properties (e.g. a Color property) - the type there is letting the deserializer know what he should create with the data. In some cases we can ā€œguessā€ it (e.g. for Material we actually do that because of the way the data is stored using a special string /material/<index_of_material_in_glb for example < that’s a json pointer. For other types we can not really know for sure)

We recommend to always put in the expected type to get rid of any guesswork and make it a habit :slightly_smiling_face: then there’s no confusion if something isnt correctly deserialized

BTW, the error is now gone but I can’t see the material on my object.
This is the updated code:

import { EventList, serializable } from "@needle-tools/engine"
import { IInteractable } from "./IInteractable";
import { Color, Material, MeshStandardMaterial } from "three";

export class InteractableObject extends IInteractable{

    /** The event that will occur opun interaction */
    @serializable(EventList) onInteraction?: EventList;
    /** The material to set the color of interactable object. Leave undefined to not apply material change */
    @serializable(MeshStandardMaterial) materialToSetColor?: MeshStandardMaterial;
    /** The color of the interactable object. */
    @serializable(Color) colorOfInteractable?: Color;

    override Interact(): () => void {
        return () => this.onInteraction?.invoke();
    }

    // coloring the object to show its interactable
    start(): void {
        if (this.materialToSetColor == undefined) return;

        if (!this.materialToSetColor) return;
        this.materialToSetColor.color = this.colorOfInteractable!;
    }
}

image.png

by user 198447544408342528

Thanks for the explanation! I will make sure to update my team of this best practice, and change the snippets file we created for the engine :slightly_smiling_face:

by user 198447544408342528

That’s now because the TS → C# compiler for Unity doesnt know that MeshStandardMaterial is meant to be a Material Type in Unity. I’ll make a note to add this! There are multiple ways to solve it. One way would be to add a comment to force the correct type in Unity (these special attributes are documented here or here

  //@type UnityEngine.Material
   @serializable(MeshStandardMaterial) materialToSetColor?: MeshStandardMaterial;

Oh amazing. Thank you! I will make sure to add this attribute :slightly_smiling_face:

by user 198447544408342528

A second way would be to leave the type in your class:

 @serializable(MeshStandardMaterial) materialToSetColor?: MeshStandardMaterial;

and instead cast it where you know it’s of a certain type:
if(this.materialToSetColor instanceof MeshStandardMaterial) this.materialToSetColor.color = this.colorOfInteractable!;

Yes it’s super useful sometimes especially if you have the same typename multiple times in Unity and want to force the component compiler to generate a specific field type! It’s not perfect but works most of the time :slightly_smiling_face:

And a third way:
if("color" in this.materialToSetColor) this.materialToSetColor.color = this.colorOfInteractable!;