3D character animation with Skeleton3D, bones, skinning, and inverse kinematics
Skeletal animation is the standard technique for animating 3D characters and articulated objects. It uses a hierarchy of bones to deform a 3D mesh, allowing for realistic character movement and animation.
The Skeleton3D node manages a hierarchy of bones used for 3D skeletal animation. Each bone has a transform relative to its parent bone, and the skeleton deforms attached meshes based on bone poses.
# Get bone countvar bone_count = $Skeleton3D.get_bone_count()print("Total bones: ", bone_count)# Find bone by namevar head_bone = $Skeleton3D.find_bone("Head")if head_bone != -1: print("Found head bone at index: ", head_bone)# Get bone namevar bone_name = $Skeleton3D.get_bone_name(0)print("First bone: ", bone_name)# Get bone parentvar parent_idx = $Skeleton3D.get_bone_parent(head_bone)if parent_idx != -1: print("Parent bone: ", $Skeleton3D.get_bone_name(parent_idx))
# Get all children of a bonevar spine_bone = $Skeleton3D.find_bone("Spine")var children = $Skeleton3D.get_bone_children(spine_bone)for child_idx in children: print("Child bone: ", $Skeleton3D.get_bone_name(child_idx))# Get root bones (bones without parents)var root_bones = $Skeleton3D.get_parentless_bones()for root_idx in root_bones: print("Root bone: ", $Skeleton3D.get_bone_name(root_idx))
The pose is the animated transform, typically set by animations:
var bone_idx = $Skeleton3D.find_bone("Head")# Get local pose (relative to parent bone)var pose = $Skeleton3D.get_bone_pose(bone_idx)print("Current pose: ", pose)# Get global pose (relative to skeleton)var global_pose = $Skeleton3D.get_bone_global_pose(bone_idx)# Set local posevar new_pose = Transform3D()new_pose.origin = Vector3(0, 0.1, 0)$Skeleton3D.set_bone_pose(bone_idx, new_pose)# Set global pose$Skeleton3D.set_bone_global_pose(bone_idx, global_pose)
# Create a skin from current rest transformsvar skin = $Skeleton3D.create_skin_from_rest_transforms()# Register skin with skeletonvar skin_ref = $Skeleton3D.register_skin(skin)# Apply skin to MeshInstance3D$MeshInstance3D.skin = skin
Attach objects (weapons, accessories) to specific bones using BoneAttachment3D:
# Create a sword attached to the handvar bone_attachment = BoneAttachment3D.new()bone_attachment.bone_name = "RightHand"$Skeleton3D.add_child(bone_attachment)# Add sword model as child of attachmentvar sword = preload("res://sword.tscn").instantiate()bone_attachment.add_child(sword)
The BoneAttachment3D automatically follows the bone’s transform.
# Simple two-bone IK (e.g., for an arm)func solve_two_bone_ik(skeleton: Skeleton3D, root_bone: String, mid_bone: String, tip_bone: String, target_pos: Vector3): var root_idx = skeleton.find_bone(root_bone) var mid_idx = skeleton.find_bone(mid_bone) var tip_idx = skeleton.find_bone(tip_bone) # Get bone lengths var upper_length = skeleton.get_bone_rest(mid_idx).origin.length() var lower_length = skeleton.get_bone_rest(tip_idx).origin.length() # Get root position in global space var root_global = skeleton.get_bone_global_pose(root_idx) var root_pos = root_global.origin # Calculate target direction and distance var target_dir = (target_pos - root_pos).normalized() var target_dist = root_pos.distance_to(target_pos) # Clamp distance to reachable range var max_reach = upper_length + lower_length target_dist = min(target_dist, max_reach * 0.99) # Law of cosines for angles var upper_angle = acos(clamp( (target_dist * target_dist + upper_length * upper_length - lower_length * lower_length) / (2 * target_dist * upper_length), -1, 1)) var mid_angle = acos(clamp( (upper_length * upper_length + lower_length * lower_length - target_dist * target_dist) / (2 * upper_length * lower_length), -1, 1)) # Apply rotations (simplified - real implementation needs proper axis calculation) # This is a basic example - production code needs more robust math
For production IK, consider using third-party addons like “Godot IK” or implementing FABRIK (Forward And Backward Reaching Inverse Kinematics) for multi-bone chains.
Godot 4 uses SkeletonModifier3D nodes for procedural bone manipulation:
# Example: Custom modifier to make character look at targetextends SkeletonModifier3Dvar target_position: Vector3var head_bone: String = "Head"func _process_modification(): var skeleton = get_skeleton() var head_idx = skeleton.find_bone(head_bone) if head_idx == -1: return # Get head global position var head_global = skeleton.get_bone_global_pose(head_idx) var look_dir = (target_position - head_global.origin).normalized() # Calculate rotation to look at target var look_transform = head_global.looking_at(target_position, Vector3.UP) # Apply rotation (with limits) skeleton.set_bone_global_pose(head_idx, look_transform)
# Retarget animation from source to target skeletonfunc retarget_animation(source_skeleton: Skeleton3D, target_skeleton: Skeleton3D, animation: Animation): var bone_map = {} # Map source bone names to target bone names # Build bone mapping for i in range(source_skeleton.get_bone_count()): var source_name = source_skeleton.get_bone_name(i) var target_idx = target_skeleton.find_bone(source_name) if target_idx != -1: bone_map[i] = target_idx # Retarget animation tracks...
# Disable bones you don't need to animatevar finger_bones = ["LeftPinky", "LeftRing", "LeftMiddle"]for bone_name in finger_bones: var bone_idx = $Skeleton3D.find_bone(bone_name) if bone_idx != -1: $Skeleton3D.set_bone_enabled(bone_idx, false)# Check if bone is enabledvar is_enabled = $Skeleton3D.is_bone_enabled(bone_idx)