Advice on changing mesh texture in Avatar Customizer

Godot Version

Godot 4.2.1 Stable

Question

I am making an avatar customizer, and I’m wondering what the best route for sake of ease and user friendliness I should take without disrupting my current script too much. Currently I’m working on the hairstyle section, and wondering how we should handle changing the color of the material on the hairstyles. Currently the hairstyles are instanced into the scene on a button press and can be removed by pressing another button. The instance button makes the hairstyle a child of a Node3d to make sure the hair sits on top of the head just right.

I guess I’m confused on how to change the material of an instanced node in general, but also wondering what would be easiest for me to make while keeping it easy for the player to use as well. Three options come to mind for the color input–A colorpicker node, an array of colors that the player can flip through, or a color pallet that players click the color they want. On top of this players can have more than one hairstyle present at a time…Really stumped.

TL;DR How do I change the color of an instanced node and what’s the best option for creating a UI for this?

Avatar Creation Screen :

Node SetUp (Each hairstyle option has a Node3D that it’s instance will be parented to with its matching name) :

image

Hairstyle Instance Script Example :

#YORK-----------------BEGIN-------------------------------------------------------------------------------------------
#INST FUNCTIONS
func yorkhair_inst():
var YorkHairScene = preload(“res://Meshes/Angels/Hair/york_hair.tscn”)
var YHinst = YorkHairScene.instantiate()
var YorkHPos = $“…/…/…/…/Base_Angel/Armature/Skeleton3D/NeckBone/YorkHPos”
YorkHPos.add_child(YHinst)
#Checking for Instance
if is_instance_valid(YHinst):
#Toggles QF button to Visible
$TabContainer/Hair/HairOptions/YorkHairPVW/YorkHairQF.visible = true
#Disables the Hair Inst Button
$TabContainer/Hair/HairOptions/YorkHairPVW/YorkHairBTN.disabled = true

func _on_york_hair_btn_pressed():
#Runs the York Hair Instance func
yorkhair_inst()
#Playing Animation
AP.play(“Posing001”)

#QUEUE FREE FUNCTION
func _on_york_hair_qf_pressed():
var YorkHpos = $“…/…/…/…/Base_Angel/Armature/Skeleton3D/NeckBone/YorkHPos”
var childNode = YorkHpos.get_child(0)
if childNode:
childNode.queue_free()
#Toggles Visibility of QF button to False (default)
$TabContainer/Hair/HairOptions/YorkHairPVW/YorkHairQF.visible = false
#Re-enables the Hair Inst Button
$TabContainer/Hair/HairOptions/YorkHairPVW/YorkHairBTN.disabled = false

#-------------------------------------------------------------------------------------------YORK------------------END

If every hairstyle is a simple MeshInstance3D (with a StandardMaterial3D) then you can get the material with dot notation, and change the color with the albedo_color property

childNode.material.albedo_color = Color8(255, 0, 0)

I would use a color picker node or a preset palette of buttons for UI.

1 Like

Thanks! I’ll have to start setting this up!

Wanted to ask a follow up question on implementing this. I set up a color palette with placeholder buttons :

Every time I click the black hair color button, I just get the “Nothing to change!” message we set up. I’m not sure if there’s a better way to do this or if I’m missing something. I tried setting the instance as well to make sure it’s a meshinstance3D, as before it was a Node3D parented to the MeshInstance3D itself. So, I changed the instance to a scene of just the meshinstance3D by itself.

Here’s the updated script with a section for the black hair color :

func yorkhair_inst():
	var YorkHairScene = preload("res://Meshes/Angels/Hair/GLB/YorkHair.glb")
	var YHinst = YorkHairScene.instantiate()
	var YorkHPos = $"../../../../Base_Angel/Armature/Skeleton3D/NeckBone/YorkHPos"
	YorkHPos.add_child(YHinst)
		#Checking for Instance
	if is_instance_valid(YHinst):
	#Toggles QF button to Visible
		$TabContainer/Hair/HairOptions/YorkHairPVW/YorkHairQF.visible = true
	#Disables the Hair Inst Button
		$TabContainer/Hair/HairOptions/YorkHairPVW/YorkHairBTN.disabled = true

func _on_black_hair_pressed():
	var YorkHpos = $"../../../../Base_Angel/Armature/Skeleton3D/NeckBone/YorkHPos"
	var childNode = YorkHpos.get_child(0)
	if childNode is MeshInstance3D: 
		childNode.material.albedo_color = Color(0,0,0)
	else : 
		print("Nothing to change!")


func _on_york_hair_btn_pressed():
	#Runs the York Hair Instance func 
	yorkhair_inst()
#Playing Animation
	AP.play("Posing001")

#QUEUE FREE FUNCTION
func _on_york_hair_qf_pressed():
	var YorkHpos = $"../../../../Base_Angel/Armature/Skeleton3D/NeckBone/YorkHPos"
	var childNode = YorkHpos.get_child(0)
	if childNode: 
		childNode.queue_free()
	#Toggles Visibility of QF button to False (default) 
	$TabContainer/Hair/HairOptions/YorkHairPVW/YorkHairQF.visible = false
	#Re-enables the Hair Inst Button
	$TabContainer/Hair/HairOptions/YorkHairPVW/YorkHairBTN.disabled = false

You can check the game’s scene tree when playing in the “remote” tab of the Scene Tree box. Also be sure to use code formatting when pasting scripts.

On a new line press the </> button to creat three ticks, then paste your script

```
type or paste code here
```

1 Like

Apologies if I’m mistaking this but, after setting up the remote debug I’m afraid my phone won’t run it. Ironically enough I was helping another dev playtest their mobile game a couple days ago, but my phone wouldn’t run it because my phone doesn’t have very much ram. My phone spat out the same error for both the dev’s beta test and my remote debug. Something about not being optimized, it even gave me an option to clear my cache for better performance, but it doesn’t seem to be working.

It was a 3D game too, so I’m wondering if that’s why. I’m a little stumped.

Is there another way to see what the node tree is doing while testing? Any other suggestions?

Sorry, this is getting a little complicated and thanks for the copy and paste script tip, I’ve been wondering how to do that for ages. :sweat_smile:

When you hit play (F5) to debug locally this tab appears, click on “remote”. This will show you the in-game scene tree, I want you to look for your YorkHPos children.

1 Like

Ah! Now I see what’s happening.

Original Remote Debug Result (Instancing the hair in) :
image

Assuming the Node3D was getting in the way, I made a new scene of just the mesh itself and tested it again.
Fixed/Current Tree :
FixedYorkHairSceneParenting_MaterialError_5-23-24

Although now when I go to test the “black hair button”, I got a different error :

Invalid get index 'material' (on base: 'MeshInstance3D').

There are a few options but I think you want material_override.

Other options are:

  1. material_overlay
  2. get_surface_override_material(int)

the 2nd one is useful if you want to change part of the mesh’s material, but it has to be created with multiple material slots.

1 Like

Material Override worked perfectly, just made the blackHair it’s own material that gets loaded in.

Working Script :

func _on_black_hair_pressed():
	var blackHair = "res://Meshes/Angels/HairColors/BlackHair.tres"
	var YorkHpos = $"../../../../Base_Angel/Armature/Skeleton3D/NeckBone/YorkHPos"
	var childNode = YorkHpos.get_child(0)
	if childNode is MeshInstance3D: 
		childNode.material_override = load(blackHair)
	else : 
		print("Nothing to change!")

Thanks for all your help again, I learned a lot from this whole thread! :grin:

1 Like