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:
2026-06-23 01:15:37 -04:00
parent 85066f71f3
commit 5e12502a18
7 changed files with 223 additions and 32 deletions
+49
View File
@@ -0,0 +1,49 @@
extends SceneTree
func _init():
print("=== GLTFDocument API Test ===")
var path = "res://assets/characters/skins/miku_rigged_animated.glb"
var file = FileAccess.open(path, FileAccess.READ)
var bytes = file.get_buffer(file.get_length())
file.close()
var gltf = GLTFDocument.new()
var state = GLTFState.new()
var err = gltf.append_from_buffer(bytes, "", state)
print("append_from_buffer: %d" % err)
if err == OK:
# List all methods
var methods = gltf.get_method_list()
print("Total methods: %d" % methods.size())
for m in methods:
var n = m.name
if n.contains("anim") or n.contains("mesh") or n.contains("scene") or n.contains("generate") or n.contains("get_") or n.contains("instantiate"):
print(" %s" % n)
# Try generate_scene
if gltf.has_method("generate_scene"):
var scene = gltf.generate_scene(state)
if scene:
print("Scene: %s" % scene.name)
get_root().add_child(scene)
else:
print("generate_scene returned null")
else:
print("No generate_scene method")
# Try instantiate
if gltf.has_method("instantiate"):
var node = gltf.instantiate(state)
if node:
print("Instantiated: %s" % node.name)
get_root().add_child(node)
else:
print("instantiate returned null")
else:
print("No instantiate method")
quit()
+38
View File
@@ -0,0 +1,38 @@
extends SceneTree
func _init():
print("=== GLTFDocument Test ===")
var path = "res://assets/characters/skins/miku_rigged_animated.glb"
if not FileAccess.file_exists(path):
print("File not found")
quit()
return
var file = FileAccess.open(path, FileAccess.READ)
var bytes = file.get_buffer(file.get_length())
file.close()
print("Read %d bytes" % bytes.size())
var gltf = GLTFDocument.new()
var state = GLTFState.new()
var err = gltf.append_from_buffer(bytes, "", state)
print("append_from_buffer returned: %d" % err)
if err == OK:
print("Animations: %d" % gltf.get_animation_count())
print("Meshes: %d" % gltf.get_mesh_count())
var scene = gltf.generate_scene(state)
if scene:
print("Scene: %s" % scene.name)
get_root().add_child(scene)
else:
print("Failed to generate scene")
else:
print("Failed to parse")
quit()
+1
View File
@@ -436,6 +436,7 @@ func _spawn_player(pid: int) -> CharacterBody3D:
skinned.scale_factor = 1.0 # Already scaled to 1.8m in Blender
skinned.position = Vector3.ZERO # Model origin = player origin
player.add_child(skinned)
print("TestLevelBuilder: added SkinnedModel for player 1")
else:
# Default: procedural humanoid
var humanoid = load("res://characters/humanoid_model.gd").new()
+36
View File
@@ -0,0 +1,36 @@
extends SceneTree
func _init():
print("=== Test GLB Load ===")
var path = "res://assets/characters/skins/miku_rigged_animated.glb"
# Read GLB
var file = FileAccess.open(path, FileAccess.READ)
var bytes = file.get_buffer(file.get_length())
file.close()
print("Read %d bytes" % bytes.size())
# Parse
var gltf = GLTFDocument.new()
var state = GLTFState.new()
var err = gltf.append_from_buffer(bytes, "", state)
print("Parse result: %d" % err)
if err == OK:
var scene = gltf.generate_scene(state)
if scene:
print("Scene: %s" % scene.name)
print("Children:")
for child in scene.get_children():
print(" %s (%s)" % [child.name, child.get_class()])
if child is Skeleton3D:
print(" Bones: %d" % child.get_bone_count())
if child is AnimationPlayer:
print(" Animations: %s" % child.get_animation_list())
get_root().add_child(scene)
print("Scene added to tree!")
else:
print("Failed to generate scene")
quit()