Godot Version
Replace this line with your Godot version
Question
Im using humanizer to build my model
func _ready():
$Humanizer.connect("character_loaded", Callable(self, "_on_character_loaded"))
spawn_character()
func spawn_character():
var config := HumanConfig.new()
config.targets["gender"] = 0.0
config.targets["age"] = 0.4
config.eye_color = Color.GREEN
config.hair_color = Color.BLACK
config.add_equipment(HumanizerEquipment.new("addons/humanizer/default_skeleton"))
config.add_equipment(HumanizerEquipment.new("Body-default", "young_caucasian_female"))
config.add_equipment(HumanizerEquipment.new("RightEyeEyeball-01"))
config.add_equipment(HumanizerEquipment.new("LeftEyeEyeball-01"))
config.add_equipment(HumanizerEquipment.new("Shoes-02"))
humanizer.load_config_async(config)
var char = humanizer.get_CharacterBody3D(false)
add_child(char)
char.global_position = Vector3(0, 1, 0)
I keep getting these errors
E 0:00:43:731 Player.gd:46 @ _ready(): In Object of type ‘Node3D’: Attempt to connect nonexistent signal ‘character_loaded’ to callable ‘CharacterBody3D(Player.gd)::_on_character_loaded’.
E 0:00:43:753 spawn_character: Invalid call. Nonexistent function ‘load_config_async’ in base ‘Node3D’.
I kept rewriting Humanizer.gd to fix errors
@tool
extends Node3D
var human_config:HumanConfig
var helper_vertex:PackedVector3Array = []
var mesh_arrays : Dictionary = {}
var materials: Dictionary = {}
var rig: HumanizerRig
var skeleton_data : Dictionary = {} #bone names with parent, position and rotation data
signal done_generating_materials
signal skeleton_changed
signal character_loaded
func load_config(config: HumanConfig):
human_config = config
# if eyes are blank, wait for done_generating_materials in calling class
func load_config_async(_human_config:HumanConfig):
var timer
materials = {}
mesh_arrays = {}
skeleton_data = {}
rig = null
human_config = _human_config
await get_tree().process_frame
load_config(_human_config)
helper_vertex = HumanizerTargetService.init_helper_vertex(human_config.targets)
HumanizerLogger.profile("load equipment", func():
for equip in human_config.equipment.values():
mesh_arrays[equip.type] = HumanizerEquipmentService.load_mesh_arrays(equip.get_type())
init_equipment_material(equip)
)
fit_all_meshes()
set_rig(human_config.rig) #this adds the rigged bones and updates all the bone weights
if not check_materials_done_generating():
await done_generating_materials
# there are race conditions in this function (i think)
func get_CharacterBody3D(baked:bool):
var human = CharacterBody3D.new()
HumanizerLogger.profile("humanizer.get_CharacterBody3D", func():
human.set_script(HumanizerResourceService.load_resource(ProjectSettings.get_setting("addons/humanizer/default_characterbody_script")))
human.human_config = human_config
var skeleton = get_skeleton()
human.add_child(skeleton)
skeleton.set_unique_name_in_owner(true)
var body_mesh = MeshInstance3D.new()
body_mesh.name = "Avatar"
if baked:
body_mesh.mesh = standard_bake_meshes()
else:
HumanizerLogger.profile("humanizer.get_CharacterBody3D.get_combined_meshes", func():
body_mesh.mesh = get_combined_meshes()
)
human.add_child(body_mesh)
body_mesh.skeleton = NodePath('../' + skeleton.name)
body_mesh.skin = skeleton.create_skin_from_rest_transforms()
var anim_player = get_animation_tree()
if anim_player != null:
human.add_child(anim_player)
anim_player.active=true
skeleton.owner = human
anim_player.owner = human
if human_config.has_component("main_collider"):
human.add_child(get_main_collider())
if human_config.has_component("ragdoll"):
add_ragdoll_colliders(skeleton)
)
return human
func get_combined_meshes() -> ArrayMesh:
var new_mesh = ArrayMesh.new()
for equip_name in mesh_arrays:
var new_arrays = get_mesh_arrays(equip_name)
if not new_arrays.is_empty():
new_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES,new_arrays)
var surface_id = new_mesh.get_surface_count()-1
new_mesh.surface_set_material(surface_id,materials[equip_name])
new_mesh.surface_set_name(surface_id,equip_name)
return new_mesh
func get_animation_tree():
var anim_tree : AnimationTree
if human_config.rig == 'default-RETARGETED':
anim_tree = HumanizerResourceService.load_resource("res://addons/humanizer/data/animations/face_animation_tree.tscn").instantiate()
elif human_config.rig.ends_with('RETARGETED'):
anim_tree = HumanizerResourceService.load_resource(ProjectSettings.get_setting("addons/humanizer/default_animation_tree")).instantiate()
else: # No example animator for specific rigs that aren't retargeted
return
#for file_name in OSPath.get_files_recursive("res://humanizer/animation/"):
#var anim_lib : AnimationLibrary = HumanizerResourceService.load_resource(file_name)
#var anim_player : AnimationPlayer = anim_tree.get_node("AnimationPlayer")
#anim_player.add_animation_library(file_name.get_file().get_basename(),anim_lib)
return anim_tree
func standard_bake_meshes():
var new_mesh = ArrayMesh.new()
var opaque = get_group_bake_arrays("opaque")
if not opaque.is_empty():
combine_surfaces_to_mesh(opaque,new_mesh)
var transparent = get_group_bake_arrays("transparent")
if not transparent.is_empty():
combine_surfaces_to_mesh(transparent,new_mesh)
HumanizerJobQueue.add_job(HumanizerMeshService.compress_material.bind(new_mesh))
return new_mesh
func combine_surfaces_to_mesh(surface_names:PackedStringArray,new_mesh:=ArrayMesh.new(),atlas_resolution:int=ProjectSettings.get_setting("atlas_resolution")):
var bake_arrays = []
var bake_mats = []
for s_name in surface_names:
var new_array = mesh_arrays[s_name].duplicate()
new_array[Mesh.ARRAY_CUSTOM0] = null
bake_arrays.append(new_array)
bake_mats.append(materials[s_name])
var surface = HumanizerMeshService.combine_surfaces(bake_arrays,bake_mats,atlas_resolution)
new_mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES,surface.arrays)
new_mesh.surface_set_material(new_mesh.get_surface_count()-1,surface.material)
return new_mesh
func get_group_bake_arrays(group_name:String): #transparent, opaque or all
var surface_names = PackedStringArray()
for s_name in mesh_arrays:
if group_name.to_lower() == "all":
surface_names.append(s_name)
elif materials[s_name].transparency == BaseMaterial3D.TRANSPARENCY_DISABLED:
if group_name.to_lower() == "opaque":
surface_names.append(s_name)
else:
if group_name.to_lower() == "transparent":
surface_names.append(s_name)
return surface_names
func set_skin_color(color:Color):
human_config.skin_color = color
var body = human_config.get_equipment_in_slot("body")
body.material_config.generate_material_3D(materials[body.type])
#material_updated.emit(body)
func set_eye_color(color:Color):
human_config.eye_color = color
var slots = ["lefteye","righteye","eyes"]
for equip in human_config.get_equipment_in_slots(slots):
equip.material_config.generate_material_3D(materials[equip.type])
#material_updated.emit(equip)
func set_hair_color(color:Color):
human_config.hair_color = color
var hair_equip = human_config.get_equipment_in_slot("hair")
if hair_equip != null:
hair_equip.material_config.generate_material_3D(materials[hair_equip.type])
set_eyebrow_color(human_config.eyebrow_color)
func set_eyebrow_color(color:Color):
human_config.eyebrow_color = color
var slots = ["lefteyebrow","righteyebrow","eyebrows"]
for eyebrow_equip in human_config.get_equipment_in_slots(slots):
materials[eyebrow_equip.type].albedo_color = color
#material_updated.emit(eyebrow_equip)
func init_equipment_material(equip:HumanizerEquipment): #called from thread
var equip_type = equip.get_type()
var material = StandardMaterial3D.new()
material.resource_local_to_scene = true
materials[equip.type] = material
var mat_config = equip.material_config
mat_config.done_generating.connect(check_materials_done_generating)
mat_config.generate_material_3D(material,mesh_arrays[equip.type])
func check_materials_done_generating():
for equip in human_config.equipment.values():
if equip.material_config.is_generating:
return false
done_generating_materials.emit()
return true
func set_equipment_material(equip:HumanizerEquipment, material_name: String)-> void:
human_config.set_equipment_material(equip,material_name)
init_equipment_material(equip)
func update_material(equip_type:String): #from the material config
var equip = human_config.equipment[equip_type]
equip.material_config.generate_material_3D(materials[equip.type],get_mesh_arrays(equip.type))
#material_updated.emit(equip)
func get_mesh(mesh_name:String):
var mesh = ArrayMesh.new()
var new_arrays = get_mesh_arrays(mesh_name)
if not new_arrays.is_empty():
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES,new_arrays)
mesh.surface_set_material(0,materials[mesh_name])
##TODO mesh transform
return mesh
func get_mesh_arrays(mesh_name:String) -> Array: # generate normals/tangents, without the cutom0
#print("getting mesh arrays")
var new_arrays = mesh_arrays[mesh_name].duplicate()
new_arrays[Mesh.ARRAY_CUSTOM0] = null
if new_arrays[Mesh.ARRAY_INDEX].is_empty():
return []
new_arrays = HumanizerMeshService.generate_normals_and_tangents(new_arrays)
return new_arrays
func add_equipment(equip:HumanizerEquipment):
var equip_type = equip.get_type()
var remove_equip = human_config.get_equipment_in_slots(equip_type.slots)
for re in remove_equip:
remove_equipment(re)
human_config.add_equipment(equip)
mesh_arrays[equip_type.resource_name] = HumanizerEquipmentService.load_mesh_arrays(equip_type)
fit_equipment_mesh(equip_type.resource_name)
if equip_type.rig_config != null:
HumanizerRigService.skeleton_add_rigged_equipment(equip,mesh_arrays[equip_type.resource_name], skeleton_data)
skeleton_changed.emit()
update_equipment_weights(equip_type.resource_name)
init_equipment_material(equip)
func remove_equipment(equip:HumanizerEquipment):
human_config.remove_equipment(equip)
var equip_type = equip.get_type()
mesh_arrays.erase(equip_type.resource_name)
if equip_type.rig_config != null:
HumanizerRigService.skeleton_remove_rigged_equipment(equip, skeleton_data)
skeleton_changed.emit()
materials.erase(equip_type.resource_name)
func get_body_mesh():
return get_mesh("Body")
func hide_clothes_vertices():
HumanizerEquipmentService.hide_vertices(human_config.equipment,mesh_arrays)
func show_clothes_vertices():
HumanizerEquipmentService.show_vertices(human_config.equipment,mesh_arrays)
func set_targets(target_data:Dictionary):
HumanizerTargetService.set_targets(target_data,human_config.targets,helper_vertex)
fit_all_meshes()
HumanizerRigService.adjust_bone_positions(skeleton_data,rig,helper_vertex,human_config.equipment,mesh_arrays)
skeleton_changed.emit()
func fit_all_meshes():
for equip_name in human_config.equipment:
fit_equipment_mesh(equip_name)
func fit_equipment_mesh(equip_name:String):
var equip:HumanizerEquipment = human_config.equipment[equip_name]
var mhclo = HumanizerResourceService.load_resource(equip.get_type().mhclo_path)
mesh_arrays[equip_name] = HumanizerEquipmentService.fit_mesh_arrays(mesh_arrays[equip_name],helper_vertex,mhclo)
func set_rig(rig_name:String):
human_config.rig = rig_name
rig = HumanizerRigService.get_rig(rig_name)
skeleton_data = rig.init_skeleton_data()
for equip in human_config.equipment.values():
if equip.get_type().is_rigged():
HumanizerRigService.skeleton_add_rigged_equipment(equip,mesh_arrays[equip.type],skeleton_data)
HumanizerRigService.adjust_bone_positions(skeleton_data,rig,helper_vertex,human_config.equipment,mesh_arrays)
#print(rig.weights.size())
update_bone_weights()
if &'root_bone' in human_config.components:
enable_root_bone_component()
skeleton_changed.emit()
func _on_character_loaded(character_body):
print("Character loaded:", character_body.name)
# Example: attach hitboxes or start animations
_attach_hitboxes(character_body)
func _attach_hitboxes(character_body: CharacterBody3D):
var skeleton: Skeleton3D = character_body.get_node("Skeleton3D")
# Bone names used by Humanizer rigs
var head_bone = skeleton.find_bone("head")
var spine_bone = skeleton.find_bone("spine")
var left_hand_bone = skeleton.find_bone("hand.L")
var right_hand_bone = skeleton.find_bone("hand.R")
_attach_hitbox_to_bone(skeleton, head_bone, "HeadHitbox", Vector3(0.25, 0.25, 0.25))
_attach_hitbox_to_bone(skeleton, spine_bone, "TorsoHitbox", Vector3(0.35, 0.45, 0.35))
_attach_hitbox_to_bone(skeleton, left_hand_bone, "LeftHandHitbox", Vector3(0.15, 0.15, 0.15))
_attach_hitbox_to_bone(skeleton, right_hand_bone, "RightHandHitbox", Vector3(0.15, 0.15, 0.15))
func _attach_hitbox_to_bone(skeleton: Skeleton3D, bone_idx: int, name: String, size: Vector3):
if bone_idx == -1:
print("Bone not found:", name)
return
var hitbox = Area3D.new()
hitbox.name = name
var shape = CollisionShape3D.new()
var box = BoxShape3D.new()
box.size = size
shape.shape = box
hitbox.add_child(shape)
skeleton.add_child(hitbox)
skeleton.set_bone_attachment(bone_idx, hitbox)
hitbox.connect("body_entered", Callable(self, "_on_hitbox_body_entered"))
func get_skeleton()->Skeleton3D:
#print(skeleton_data)
var skeleton = HumanizerRigService.get_skeleton_3D(skeleton_data)
skeleton.motion_scale = HumanizerRigService.get_motion_scale(human_config.rig,helper_vertex)
#skeleton.motion_scale = 1
return skeleton
func rebuild_skeleton(skeleton:Skeleton3D):
HumanizerRigService.rebuild_skeleton_3D(skeleton,skeleton_data)
func adjust_skeleton(skeleton:Skeleton3D):
HumanizerRigService.adjust_skeleton_3D(skeleton,skeleton_data)
skeleton.motion_scale = HumanizerRigService.get_motion_scale(human_config.rig,helper_vertex)
func update_bone_weights():
for equip_name in human_config.equipment:
update_equipment_weights(equip_name)
func update_equipment_weights(equip_name:String):
var equip_type:HumanizerEquipmentType = human_config.equipment[equip_name].get_type()
var mhclo = HumanizerResourceService.load_resource(equip_type.mhclo_path)
if equip_type.is_rigged():
var rigged_bone_weights = {}
rigged_bone_weights.bones = equip_type.rig_config.bones
rigged_bone_weights.weights = equip_type.rig_config.weights
var rig_bw = HumanizerEquipmentService.interpolate_rigged_weights(equip_type,rigged_bone_weights,rig)
#equip_type.rig_config.bones[rig_name] = rig_bw.bones
#equip_type.rig_config.weights[rig_name] = rig_bw.weights
var bones = rig_bw.bones.duplicate() #could potentially have multiple of the same mhclo open, dont want to change other arrays (due to godot resource sharing)
for bone_array_id in bones.size():
var bone_id = bones[bone_array_id]
if bone_id < 0:
bone_id = skeleton_data.keys().find( equip_type.rig_config.config[(bone_id +1) *-1].name) #offset by one because -0 = 0
bones[bone_array_id] = bone_id
mesh_arrays[equip_name][Mesh.ARRAY_BONES] = bones
mesh_arrays[equip_name][Mesh.ARRAY_WEIGHTS] = rig_bw.weights
else:
var bone_weights = HumanizerEquipmentService.interpolate_weights( mhclo,rig)
mesh_arrays[equip_name][Mesh.ARRAY_BONES] = bone_weights.bones
mesh_arrays[equip_name][Mesh.ARRAY_WEIGHTS] = bone_weights.weights
#mesh_arrays[equip_name][Mesh.ARRAY_BONES] = mhclo.bones[rig.resource_name.replace("-RETARGETED","")].duplicate()
#mesh_arrays[equip_name][Mesh.ARRAY_WEIGHTS] = mhclo.weights[rig.resource_name.replace("-RETARGETED","")].duplicate()
func enable_root_bone_component():
human_config.enable_component(&'root_bone')
if "Root" not in skeleton_data:
skeleton_data.Root = {local_xform=Transform3D(),global_pos=Vector3(0,0,0)}
skeleton_data[skeleton_data.keys()[0]].parent = "Root"
func disable_root_bone_component():
human_config.disable_component(&'root_bone')
if "Root" in skeleton_data and "game_engine" not in human_config.rig:
skeleton_data.erase("Root")
skeleton_data[skeleton_data.keys()[0]].erase("parent")
func get_main_collider():
return HumanizerColliderService.get_main_collider(helper_vertex)
func adjust_main_collider(main_collider:CollisionShape3D):
HumanizerColliderService.adjust_main_collider(helper_vertex,main_collider)
func add_ragdoll_colliders(skeleton:Skeleton3D,ragdoll_layers = ProjectSettings.get_setting("addons/humanizer/physical_bone_layers"),ragdoll_mask=ProjectSettings.get_setting("addons/humanizer/physical_bone_mask")):
skeleton.reset_bone_poses()
HumanizerPhysicalSkeleton.new(skeleton, helper_vertex, ragdoll_layers, ragdoll_mask).run()
skeleton.reset_bone_poses()
func get_foot_offset()->float:
return HumanizerBodyService.get_foot_offset(helper_vertex)
func get_hips_height()->float:
return HumanizerBodyService.get_hips_height(helper_vertex)
func get_head_height()->float:
return HumanizerBodyService.get_head_height(helper_vertex)
func get_max_width()->float:
return HumanizerBodyService.get_max_width(helper_vertex)
Whats going on with this???