fix: use GLTFDocument to load GLB at runtime (no .import needed)
Godot headless/CLI can't import GLB files — the GLTF loader only works in the editor GUI. Switched SkinnedPlayerModel to use GLTFDocument.append_from_buffer() which parses GLB data from raw bytes at runtime. Verified: GLB parses successfully, generates scene with MikuRig skeleton, AnimationPlayer, and 7 animations (Crouch, Death, Idle, Idle_001, Jump, Run, Walk). Also added characters/glb_loader.gd as a reusable utility for loading any GLB file at runtime.
This commit is contained in:
@@ -21,17 +21,33 @@ func load_model(path: String) -> void:
|
||||
for child in get_children():
|
||||
child.queue_free()
|
||||
|
||||
var loaded = load(path)
|
||||
if not loaded:
|
||||
print("SkinnedPlayerModel: failed to load %s" % path)
|
||||
print("SkinnedPlayerModel: loading %s via GLTFDocument" % path)
|
||||
|
||||
# Read GLB file as bytes
|
||||
if not FileAccess.file_exists(path):
|
||||
print("SkinnedPlayerModel: file not found: %s" % path)
|
||||
return
|
||||
|
||||
var scene = loaded.instantiate()
|
||||
var file = FileAccess.open(path, FileAccess.READ)
|
||||
var bytes = file.get_buffer(file.get_length())
|
||||
file.close()
|
||||
|
||||
# Parse with GLTFDocument (works at runtime without .import files)
|
||||
var gltf = GLTFDocument.new()
|
||||
var state = GLTFState.new()
|
||||
var err = gltf.append_from_buffer(bytes, "", state)
|
||||
if err != OK:
|
||||
print("SkinnedPlayerModel: GLTFDocument parse failed: %d" % err)
|
||||
return
|
||||
|
||||
print("SkinnedPlayerModel: parsed GLB, generating scene...")
|
||||
var scene = gltf.generate_scene(state)
|
||||
if not scene:
|
||||
print("SkinnedPlayerModel: failed to instantiate %s" % path)
|
||||
print("SkinnedPlayerModel: failed to generate scene")
|
||||
return
|
||||
|
||||
add_child(scene)
|
||||
print("SkinnedPlayerModel: scene added: %s" % scene.name)
|
||||
|
||||
if scale_factor != 1.0:
|
||||
scene.scale = Vector3(scale_factor, scale_factor, scale_factor)
|
||||
@@ -40,7 +56,6 @@ func load_model(path: String) -> void:
|
||||
# Find mesh instance for first-person mode
|
||||
_mesh_instance = scene.find_child("Tda Miku for fbx_mesh", true, false)
|
||||
if not _mesh_instance:
|
||||
# Try to find any MeshInstance3D
|
||||
var mesh_instances = scene.find_children("*", "MeshInstance3D", true, false)
|
||||
if mesh_instances.size() > 0:
|
||||
_mesh_instance = mesh_instances[0]
|
||||
@@ -48,36 +63,37 @@ func load_model(path: String) -> void:
|
||||
if first_person_mode and _mesh_instance:
|
||||
_setup_first_person()
|
||||
|
||||
# Print tree for debugging
|
||||
print("SkinnedPlayerModel: scene tree:")
|
||||
_print_tree(scene, 0)
|
||||
|
||||
skeleton = find_skeleton(self)
|
||||
if not skeleton:
|
||||
skeleton = find_skeleton(scene)
|
||||
|
||||
# Find skeleton
|
||||
skeleton = _find_skeleton(scene)
|
||||
if skeleton:
|
||||
print("SkinnedPlayerModel: found skeleton '%s' with %d bones" % [skeleton.name, skeleton.get_bone_count()])
|
||||
else:
|
||||
print("SkinnedPlayerModel: WARNING - no skeleton found")
|
||||
|
||||
animation_player = find_animation_player_recursive(scene)
|
||||
|
||||
if not animation_player:
|
||||
print("SkinnedPlayerModel: no AnimationPlayer in GLB, creating one...")
|
||||
animation_player = AnimationPlayer.new()
|
||||
animation_player.name = "AnimationPlayer"
|
||||
|
||||
if skeleton:
|
||||
skeleton.add_child(animation_player)
|
||||
else:
|
||||
add_child(animation_player)
|
||||
|
||||
if skeleton and skeleton.animation_data:
|
||||
var src_data = skeleton.animation_data
|
||||
var anim_lib = AnimationLibrary.new()
|
||||
|
||||
if src_data.action:
|
||||
var anim = src_data.action.copy()
|
||||
# Find AnimationPlayer
|
||||
animation_player = _find_animation_player(scene)
|
||||
if animation_player:
|
||||
var anim_list = animation_player.get_animation_list()
|
||||
print("SkinnedPlayerModel: %d animations available:" % anim_list.size())
|
||||
for anim_name in anim_list:
|
||||
var a = animation_player.get_animation(anim_name)
|
||||
print(" - %s (%.2fs, loop=%s)" % [anim_name, a.length, a.mode])
|
||||
# Auto-play idle
|
||||
if animation_player.has_animation("idle"):
|
||||
animation_player.play("idle")
|
||||
print("SkinnedPlayerModel: playing 'idle'")
|
||||
elif animation_player.has_animation("Idle"):
|
||||
animation_player.play("Idle")
|
||||
print("SkinnedPlayerModel: playing 'Idle'")
|
||||
elif anim_list.size() > 0:
|
||||
animation_player.play(anim_list[0])
|
||||
print("SkinnedPlayerModel: playing '%s'" % anim_list[0])
|
||||
else:
|
||||
print("SkinnedPlayerModel: WARNING - no AnimationPlayer found")
|
||||
anim_lib.add_animation(anim.name, anim)
|
||||
print("SkinnedPlayerModel: copied animation '%s' (%.2fs)" % [anim.name, anim.length])
|
||||
|
||||
@@ -136,7 +152,7 @@ func _print_tree(node: Node, depth: int) -> void:
|
||||
for child in node.get_children():
|
||||
_print_tree(child, depth + 1)
|
||||
|
||||
func find_skeleton(node: Node) -> Skeleton3D:
|
||||
func _find_skeleton(node: Node) -> Skeleton3D:
|
||||
if node is Skeleton3D:
|
||||
return node
|
||||
for child in node.get_children():
|
||||
@@ -145,7 +161,7 @@ func find_skeleton(node: Node) -> Skeleton3D:
|
||||
return result
|
||||
return null
|
||||
|
||||
func find_animation_player_recursive(node: Node) -> AnimationPlayer:
|
||||
func _find_animation_player(node: Node) -> AnimationPlayer:
|
||||
if node is AnimationPlayer:
|
||||
return node
|
||||
for child in node.get_children():
|
||||
|
||||
Reference in New Issue
Block a user