fix: create proper Idle animation in Godot when GLB export loses tracks

The Blender GLTF exporter was stripping animation tracks (only 1 of 51
channels survived export). Now SkinnedPlayerModel detects this at
runtime and creates a proper Idle animation programmatically using Godot's
Animation API with 5 bone tracks (Spine, Arms, Neck, Hips).

Also switched model_path back to miku_rigged_final.glb since the
re-export wasn't adding usable animations.
This commit is contained in:
2026-06-23 18:55:12 -04:00
parent 0b0497e128
commit 39063bfe9e
5 changed files with 275 additions and 202 deletions
+76
View File
@@ -92,9 +92,85 @@ func load_model(path: String) -> void:
elif anim_list.size() > 0:
animation_player.play(anim_list[0])
print("SkinnedPlayerModel: playing '%s'" % anim_list[0])
# If the loaded animations are too subtle (only 1 track), create better ones
if animation_player.has_animation("Idle"):
var idle_anim = animation_player.get_animation("Idle")
if idle_anim.get_track_count() <= 1:
print("SkinnedPlayerModel: Idle has too few tracks, creating a proper one")
_create_idle_animation(animation_player)
animation_player.play("Idle")
print("SkinnedPlayerModel: playing newly created Idle")
else:
print("SkinnedPlayerModel: WARNING - no AnimationPlayer found")
func _create_idle_animation(ap: AnimationPlayer) -> void:
# Create a proper idle animation with visible movement
var anim = Animation.new()
anim.length = 2.0
anim.loop_mode = Animation.LOOP_LINEAR
# Get the skeleton path from the scene
var skel_path = "MikuRig/Skeleton3D"
# Spine sway
_create_bone_track(anim, skel_path + ":Spine", "rotation",
[
{ "time": 0.0, "value": Vector3(0.1, 0, 0) },
{ "time": 0.5, "value": Vector3(-0.1, 0, 0) },
{ "time": 1.0, "value": Vector3(0.1, 0, 0) },
{ "time": 1.5, "value": Vector3(-0.1, 0, 0) },
{ "time": 2.0, "value": Vector3(0.1, 0, 0) },
])
# Left arm sway
_create_bone_track(anim, skel_path + ":LeftUpperArm", "rotation",
[
{ "time": 0.0, "value": Vector3(0, 0, 0.3) },
{ "time": 1.0, "value": Vector3(0, 0, 0.5) },
{ "time": 2.0, "value": Vector3(0, 0, 0.3) },
])
# Right arm sway
_create_bone_track(anim, skel_path + ":RightUpperArm", "rotation",
[
{ "time": 0.0, "value": Vector3(0, 0, -0.3) },
{ "time": 1.0, "value": Vector3(0, 0, -0.5) },
{ "time": 2.0, "value": Vector3(0, 0, -0.3) },
])
# Neck gentle movement
_create_bone_track(anim, skel_path + ":Neck", "rotation",
[
{ "time": 0.0, "value": Vector3(0.05, 0, 0) },
{ "time": 1.0, "value": Vector3(-0.05, 0, 0) },
{ "time": 2.0, "value": Vector3(0.05, 0, 0) },
])
# Hips subtle bounce
_create_bone_track(anim, skel_path + ":Hips", "position",
[
{ "time": 0.0, "value": Vector3(0, 0, 0) },
{ "time": 0.5, "value": Vector3(0, 0.02, 0) },
{ "time": 1.0, "value": Vector3(0, 0, 0) },
{ "time": 1.5, "value": Vector3(0, 0.02, 0) },
{ "time": 2.0, "value": Vector3(0, 0, 0) },
])
# Add animation to the player
ap.add_animation("Idle", anim)
print("SkinnedPlayerModel: created Idle animation with " + str(anim.get_track_count()) + " tracks")
func _create_bone_track(anim: Animation, bone_path: String, prop: String, keyframes: Array) -> void:
var track_idx = anim.add_track(Animation.TYPE_VALUE)
anim.track_set_path(track_idx, bone_path + ":" + prop)
anim.track_set_interpolation_type(track_idx, Animation.INTERPOLATION_LINEAR)
for kf in keyframes:
var time = kf["time"]
var value = kf["value"]
anim.track_insert_key(track_idx, time, value)
func _setup_first_person() -> void:
# In first person, hide the head and upper body so only arms/hands/legs show
# This creates the classic FPS arms-only view