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!;