These are tested personal samples/notes in-progress that may evolve into examples or a demonstration for general use.
Here we create AnimationTree under male_base_mesh, assign BlendTree as tree_root, then we setup animations from AnimationPlayer and connect one of the animations to the output node. All programatically.
male_base_mesh
is a basic Node3D
(.glb scene)
func _ready() -> void:
var anim_tree = AnimationTree.new()
$male_base_mesh.add_child(anim_tree)
# Creating BlendTree as a AnimationTree Root Node
# Attaching animation player to the AnimationTree.
var tree_root = AnimationNodeBlendTree.new()
anim_tree.tree_root = tree_root
anim_tree.anim_player = $male_base_mesh/AnimationPlayer.get_path()
# Creating simple animation nodes and
# linking animations from AnimationPlayer
var animations = ["idle", "Walking", "mining"]
for anim_name in animations:
var anim = AnimationNodeAnimation.new()
tree_root.add_node(anim_name, anim)
anim.animation = anim_name
# Connecting one of the animation nodes to the output node.
tree_root.connect_node("output", 0, "Walking")
An AnimationNodeOutput node named
output
is created by default.
AnimationNodeBlendTree - Godot Documentation.
How to add a Blend2 node with a node name “Blend2”:
tree_root.add_node("Blend2", AnimationNodeBlend2.new())
How to resolve AnimationTree having dynamic node name:
var anim_tree = AnimationTree.new()
anim_tree.name = "AnimationTree"
Version 2: Adding Blend2 and AnimationTree node naming
After applying the previous newfound ideas we will have something like this:
func _ready() -> void:
var anim_tree = AnimationTree.new()
$male_base_mesh.add_child(anim_tree)
# Creating BlendTree as a AnimationTree Root Node
# Attaching animation player to the AnimationTree.
var tree_root = AnimationNodeBlendTree.new()
anim_tree.name = "AnimationTree"
anim_tree.tree_root = tree_root
anim_tree.anim_player = $male_base_mesh/AnimationPlayer.get_path()
# How to add a Blend2 node with a node name “Blend2”:
tree_root.add_node("Blend2", AnimationNodeBlend2.new())
# Creating simple animation nodes and
# linking animations from AnimationPlayer
var animations = ["idle", "Walking", "mining"]
for anim_name in animations:
var anim = AnimationNodeAnimation.new()
tree_root.add_node(anim_name, anim)
anim.animation = anim_name
# Connecting one of the animation nodes to the output node.
tree_root.connect_node("output", 0, "Walking")
This is how it may look at runtime, I don’t think Godot AnimationTree UI have support for runtime, so this is only my proven sketch of what it looks like:
Now we have very basic AnimationTree that is already working:
If you run the project scene, the “Walking” animation will play once into the output
.
Version 3: Blend2 node setup
In this version we will start to use Blend2 and begin a real world application of blending
animations. The previous versions were more of a basics demonstration.
Blend2 General
How to set blend_amount for blend2 node:
anim_tree.set("parameters/Blend2/blend_amount", 0.5)
How to connect idle animation as input of Blend2 and then blend Walking:
tree_root.connect_node("Blend2", 0, "idle")
tree_root.connect_node("Blend2", 1, "Walking")
tree_root.connect_node("output", 0, "Blend2")
Blend2 Bone filtering
How to enable bone filtering for blend2 node:
tree_root.get_node("Blend2").filter_enabled = true
How to activate filter for a bone or bones.
# ---- FILTER SPECIFIC BONES ----
var filter_bones = [
"thigh.L", "shin.L", "foot.L", "toe.L",
"thigh.R", "shin.R", "foot.R", "toe.R"
]
for bone in filter_bones:
tree_root.get_node("Blend2").set_filter_path("metarig/Skeleton3D:" + bone, true)
Bone filtering debug/test/demo script that applies bone filter to each bone every second.
func _ready() -> void:
var anim_tree = AnimationTree.new()
anim_tree.name = "AnimationTree"
$male_base_mesh.add_child(anim_tree)
var tree_root = AnimationNodeBlendTree.new()
anim_tree.tree_root = tree_root
anim_tree.anim_player = $male_base_mesh/AnimationPlayer.get_path()
var animations = ["idle", "Walking", "mining"]
for anim_name in animations:
var anim = AnimationNodeAnimation.new()
tree_root.add_node(anim_name, anim)
anim.animation = anim_name
tree_root.add_node("Blend2", AnimationNodeBlend2.new())
tree_root.get_node("Blend2").filter_enabled = true
anim_tree.set("parameters/Blend2/blend_amount", 0.8)
tree_root.connect_node("Blend2", 0, "mining")
tree_root.connect_node("Blend2", 1, "Walking")
tree_root.connect_node("output", 0, "Blend2")
anim_tree.active = true
# Collect all bone names from the skeleton
var skeleton := $male_base_mesh/metarig/Skeleton3D
if skeleton is Skeleton3D:
for i in range(skeleton.get_bone_count()):
bone_names.append(skeleton.get_bone_name(i))
# Set up the timer
var timer = Timer.new()
timer.wait_time = 1.0
timer.one_shot = false
timer.autostart = true
timer.name = "BoneDebugTimer"
add_child(timer)
timer.timeout.connect(_on_bone_debug_tick.bind(tree_root))
func _on_bone_debug_tick(tree_root: AnimationNodeBlendTree) -> void:
if bone_index >= bone_names.size():
$BoneDebugTimer.stop()
print("Bone filtering test finished.")
return
var bone_name = bone_names[bone_index]
bone_index += 1
var path = NodePath("metarig/Skeleton3D:" + bone_name)
tree_root.get_node("Blend2").set_filter_path(path, true)
print("Filtered bone:", bone_name)
Bone filtering debug/test/demo script, recursive bone filtering.
func _ready() -> void:
var anim_tree = AnimationTree.new()
anim_tree.name = "AnimationTree"
$male_base_mesh.add_child(anim_tree)
var tree_root = AnimationNodeBlendTree.new()
anim_tree.tree_root = tree_root
anim_tree.anim_player = $male_base_mesh/AnimationPlayer.get_path()
var animations = ["idle", "Walking", "mining"]
for anim_name in animations:
var anim = AnimationNodeAnimation.new()
tree_root.add_node(anim_name, anim)
anim.animation = anim_name
tree_root.add_node("Blend2", AnimationNodeBlend2.new())
tree_root.get_node("Blend2").filter_enabled = true
anim_tree.set("parameters/Blend2/blend_amount", 1)
tree_root.connect_node("Blend2", 0, "idle")
tree_root.connect_node("Blend2", 1, "Walking")
tree_root.connect_node("output", 0, "Blend2")
anim_tree.active = true
# ---- FILTER SPECIFIC BONES ----
var skeleton := $male_base_mesh/metarig/Skeleton3D
var blend_node := tree_root.get_node("Blend2")
var left_leg_paths = get_bone_and_children_paths(skeleton, "thigh.L")
for path in left_leg_paths:
blend_node.set_filter_path(path, true)
var right_leg_paths = get_bone_and_children_paths(skeleton, "thigh.R")
for path in right_leg_paths:
blend_node.set_filter_path(path, true)
print("Applied filtering to left and right leg bones.")
# Helper to collect a bone and all its children as NodePaths
func get_bone_and_children_paths(skeleton: Skeleton3D, bone_name: String, base_path: String = "metarig/Skeleton3D") -> Array:
var result: Array = []
var bone_index := skeleton.find_bone(bone_name)
if bone_index == -1:
push_warning("Bone not found: " + bone_name)
return result
_collect_bone_children(skeleton, bone_index, base_path, result)
return result
# Recursive function to gather all child bones
func _collect_bone_children(skeleton: Skeleton3D, index: int, base_path: String, result: Array) -> void:
var name = skeleton.get_bone_name(index)
result.append(NodePath(base_path + ":" + name))
for child in skeleton.get_bone_children(index):
_collect_bone_children(skeleton, child, base_path, result)
Bone filtering debug/test/demo script, recursive bone lister
func _ready() -> void:
var skeleton := $male_base_mesh/metarig/Skeleton3D
var bone_name := "shoulder.R"
var result := get_bone_and_children_names(skeleton, bone_name)
print(result)
func get_bone_and_children_names(skeleton: Skeleton3D, bone_name: String) -> Array:
var result: Array = []
var bone_index := skeleton.find_bone(bone_name)
if bone_index == -1:
push_warning("Bone not found: " + bone_name)
return result
_collect_bone_names(skeleton, bone_index, result)
return result
func _collect_bone_names(skeleton: Skeleton3D, index: int, result: Array) -> void:
var name := skeleton.get_bone_name(index)
result.append(name)
for child in skeleton.get_bone_children(index):
_collect_bone_names(skeleton, child, result)
Blend2 Testing Misc
How to enable use_custom_timeline for animation node and enable animation node linear loop:
tree_root.get_node("idle").use_custom_timeline = true
tree_root.get_node("idle").loop_mode = 1
Note: if animation is not cycling smoothly,
stretch_time_scale
may be useful to enable, if it’s not enabled by default. In my sample it was not needed to be enabled or is enabled by default.
Blend2 Setup sample
Here is entire sample that incorporates all the previous foundings to achieve blending between idle animation and Walking animation nodes.
func _ready() -> void:
var anim_tree = AnimationTree.new()
anim_tree.name = "AnimationTree"
$male_base_mesh.add_child(anim_tree)
var tree_root = AnimationNodeBlendTree.new()
anim_tree.tree_root = tree_root
anim_tree.anim_player = $male_base_mesh/AnimationPlayer.get_path()
var animations = ["idle", "Walking", "mining"]
for anim_name in animations:
var anim = AnimationNodeAnimation.new()
tree_root.add_node(anim_name, anim)
anim.animation = anim_name
tree_root.get_node("idle").use_custom_timeline = true
tree_root.get_node("idle").loop_mode = 1
tree_root.add_node("Blend2", AnimationNodeBlend2.new())
tree_root.get_node("Blend2").filter_enabled = true
anim_tree.set("parameters/Blend2/blend_amount", 1)
tree_root.connect_node("Blend2", 0, "idle")
tree_root.connect_node("Blend2", 1, "Walking")
tree_root.connect_node("output", 0, "Blend2")
# ---- FILTER SPECIFIC BONES ----
var filter_bones = [
"thigh.L", "shin.L", "foot.L", "toe.L",
"thigh.R", "shin.R", "foot.R", "toe.R"
]
for bone in filter_bones:
tree_root.get_node("Blend2").set_filter_path("metarig/Skeleton3D:" + bone, true)
Result:
A minor upperbody swaying (idle) + legs movements (Walking)
Behind scenes:
Blend2 Chained Setup sample
Now we can go on and introduce more Blend2 nodes and connect them.
This way the idle, the Walking and mining animations all blended.
func _ready() -> void:
var anim_tree = AnimationTree.new()
anim_tree.name = "AnimationTree"
$male_base_mesh.add_child(anim_tree)
var tree_root = AnimationNodeBlendTree.new()
anim_tree.tree_root = tree_root
anim_tree.anim_player = $male_base_mesh/AnimationPlayer.get_path()
var animations = ["idle", "Walking", "mining"]
for anim_name in animations:
var anim = AnimationNodeAnimation.new()
tree_root.add_node(anim_name, anim)
anim.animation = anim_name
tree_root.get_node("idle").use_custom_timeline = true
tree_root.get_node("idle").loop_mode = 1
#Walking animation was exported with loop -cycle suffix https://docs.godotengine.org/en/4.3/tutorials/assets_pipeline/importing_3d_scenes/node_type_customization.html#animation-loop-loop-cycle
#tree_root.get_node("Walking").use_custom_timeline = true
#tree_root.get_node("Walking").loop_mode = 1
tree_root.get_node("mining").use_custom_timeline = true
tree_root.get_node("mining").loop_mode = 1
# ---- First Blend2 node ----
tree_root.add_node("Blend2-1", AnimationNodeBlend2.new())
tree_root.get_node("Blend2-1").filter_enabled = true
anim_tree.set("parameters/Blend2-1/blend_amount", 1)
tree_root.connect_node("Blend2-1", 0, "idle")
tree_root.connect_node("Blend2-1", 1, "Walking")
# ---- FILTER SPECIFIC BONES Blend2-1 ----
var filter_bones = [
"thigh.L", "shin.L", "foot.L", "toe.L",
"thigh.R", "shin.R", "foot.R", "toe.R"
]
for bone in filter_bones:
tree_root.get_node("Blend2-1").set_filter_path("metarig/Skeleton3D:" + bone, true)
# ---- Second Blend2 node ----
tree_root.add_node("Blend2-2", AnimationNodeBlend2.new())
tree_root.get_node("Blend2-2").filter_enabled = true
anim_tree.set("parameters/Blend2-2/blend_amount", 1)
tree_root.connect_node("Blend2-2", 0, "Blend2-1")
tree_root.connect_node("Blend2-2", 1, "mining")
tree_root.connect_node("output", 0, "Blend2-2")
# ---- FILTER SPECIFIC BONES Blend2-2 ----
filter_bones = [
"shoulder.L", "upper_arm.L", "forearm.L", "hand.L", "f_index.01.L", "f_index.02.L", "f_index.03.L", "thumb.01.L", "thumb.02.L", "thumb.03.L", "f_middle.01.L",
"shoulder.R", "upper_arm.R", "forearm.R", "hand.R", "f_index.01.R", "f_index.02.R", "f_index.03.R", "thumb.01.R", "thumb.02.R", "thumb.03.R", "f_middle.01.R", "f_middle.02.R", "f_middle.03.R", "f_ring.01.R", "f_ring.02.R", "f_ring.03.R", "f_pinky.01.R", "f_pinky.02.R", "f_pinky.03.R"
]
for bone in filter_bones:
tree_root.get_node("Blend2-2").set_filter_path("metarig/Skeleton3D:" + bone, true)