Feat/14 movement overhaul #20

Merged
Dotts merged 86 commits from feat/14-movement-overhaul into main 2026-07-17 10:44:23 -07:00
7 changed files with 223 additions and 32 deletions
Showing only changes of commit 5e12502a18 - Show all commits
+50
View File
@@ -0,0 +1,50 @@
extends RefCounted
class_name GLBLoader
## Loads a GLB file at runtime using GLTFDocument.
## Usage:
## var loader = GLBLoader.new()
## var scene = loader.load("res://path/to/model.glb")
## if scene:
## get_tree().current_scene.add_child(scene)
static func load(glb_path: String) -> Node3D:
print("GLBLoader: loading " + glb_path)
# Check if file exists
if not FileAccess.file_exists(glb_path):
print("GLBLoader: file not found: " + glb_path)
return null
# Read file as bytes
var file = FileAccess.open(glb_path, FileAccess.READ)
if not file:
print("GLBLoader: failed to open file")
return null
var bytes = file.get_buffer(file.get_length())
file.close()
print("GLBLoader: read %d bytes" % bytes.size())
# Parse with GLTFDocument
var gltf = GLTFDocument.new()
var state = GLTFState.new()
var err = gltf.append_from_buffer(bytes, "", state)
if err != OK:
print("GLBLoader: failed to parse GLB, error: %d" % err)
return null
print("GLBLoader: parsed successfully")
print(" Animations: %d" % gltf.get_animation_count())
print(" Meshes: %d" % gltf.get_mesh_count())
# Generate scene
var scene = gltf.generate_scene(state)
if not scene:
print("GLBLoader: failed to generate scene")
return null
print("GLBLoader: generated scene: %s" % scene.name)
return scene
+46 -30
View File
@@ -21,17 +21,33 @@ func load_model(path: String) -> void:
for child in get_children(): for child in get_children():
child.queue_free() child.queue_free()
var loaded = load(path) print("SkinnedPlayerModel: loading %s via GLTFDocument" % path)
if not loaded:
print("SkinnedPlayerModel: failed to load %s" % path) # Read GLB file as bytes
if not FileAccess.file_exists(path):
print("SkinnedPlayerModel: file not found: %s" % path)
return 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: if not scene:
print("SkinnedPlayerModel: failed to instantiate %s" % path) print("SkinnedPlayerModel: failed to generate scene")
return return
add_child(scene) add_child(scene)
print("SkinnedPlayerModel: scene added: %s" % scene.name)
if scale_factor != 1.0: if scale_factor != 1.0:
scene.scale = Vector3(scale_factor, scale_factor, scale_factor) 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 # Find mesh instance for first-person mode
_mesh_instance = scene.find_child("Tda Miku for fbx_mesh", true, false) _mesh_instance = scene.find_child("Tda Miku for fbx_mesh", true, false)
if not _mesh_instance: if not _mesh_instance:
# Try to find any MeshInstance3D
var mesh_instances = scene.find_children("*", "MeshInstance3D", true, false) var mesh_instances = scene.find_children("*", "MeshInstance3D", true, false)
if mesh_instances.size() > 0: if mesh_instances.size() > 0:
_mesh_instance = mesh_instances[0] _mesh_instance = mesh_instances[0]
@@ -48,36 +63,37 @@ func load_model(path: String) -> void:
if first_person_mode and _mesh_instance: if first_person_mode and _mesh_instance:
_setup_first_person() _setup_first_person()
# Print tree for debugging
print("SkinnedPlayerModel: scene tree:") print("SkinnedPlayerModel: scene tree:")
_print_tree(scene, 0) _print_tree(scene, 0)
skeleton = find_skeleton(self) # Find skeleton
if not skeleton: skeleton = _find_skeleton(scene)
skeleton = find_skeleton(scene)
if skeleton: if skeleton:
print("SkinnedPlayerModel: found skeleton '%s' with %d bones" % [skeleton.name, skeleton.get_bone_count()]) print("SkinnedPlayerModel: found skeleton '%s' with %d bones" % [skeleton.name, skeleton.get_bone_count()])
else: else:
print("SkinnedPlayerModel: WARNING - no skeleton found") print("SkinnedPlayerModel: WARNING - no skeleton found")
animation_player = find_animation_player_recursive(scene) # Find AnimationPlayer
animation_player = _find_animation_player(scene)
if not animation_player: if animation_player:
print("SkinnedPlayerModel: no AnimationPlayer in GLB, creating one...") var anim_list = animation_player.get_animation_list()
animation_player = AnimationPlayer.new() print("SkinnedPlayerModel: %d animations available:" % anim_list.size())
animation_player.name = "AnimationPlayer" for anim_name in anim_list:
var a = animation_player.get_animation(anim_name)
if skeleton: print(" - %s (%.2fs, loop=%s)" % [anim_name, a.length, a.mode])
skeleton.add_child(animation_player) # Auto-play idle
else: if animation_player.has_animation("idle"):
add_child(animation_player) animation_player.play("idle")
print("SkinnedPlayerModel: playing 'idle'")
if skeleton and skeleton.animation_data: elif animation_player.has_animation("Idle"):
var src_data = skeleton.animation_data animation_player.play("Idle")
var anim_lib = AnimationLibrary.new() print("SkinnedPlayerModel: playing 'Idle'")
elif anim_list.size() > 0:
if src_data.action: animation_player.play(anim_list[0])
var anim = src_data.action.copy() print("SkinnedPlayerModel: playing '%s'" % anim_list[0])
else:
print("SkinnedPlayerModel: WARNING - no AnimationPlayer found")
anim_lib.add_animation(anim.name, anim) anim_lib.add_animation(anim.name, anim)
print("SkinnedPlayerModel: copied animation '%s' (%.2fs)" % [anim.name, anim.length]) 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(): for child in node.get_children():
_print_tree(child, depth + 1) _print_tree(child, depth + 1)
func find_skeleton(node: Node) -> Skeleton3D: func _find_skeleton(node: Node) -> Skeleton3D:
if node is Skeleton3D: if node is Skeleton3D:
return node return node
for child in node.get_children(): for child in node.get_children():
@@ -145,7 +161,7 @@ func find_skeleton(node: Node) -> Skeleton3D:
return result return result
return null return null
func find_animation_player_recursive(node: Node) -> AnimationPlayer: func _find_animation_player(node: Node) -> AnimationPlayer:
if node is AnimationPlayer: if node is AnimationPlayer:
return node return node
for child in node.get_children(): for child in node.get_children():
+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.scale_factor = 1.0 # Already scaled to 1.8m in Blender
skinned.position = Vector3.ZERO # Model origin = player origin skinned.position = Vector3.ZERO # Model origin = player origin
player.add_child(skinned) player.add_child(skinned)
print("TestLevelBuilder: added SkinnedModel for player 1")
else: else:
# Default: procedural humanoid # Default: procedural humanoid
var humanoid = load("res://characters/humanoid_model.gd").new() 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()
+3 -2
View File
@@ -148,9 +148,10 @@ func _spawn_player(pid: int) -> CharacterBody3D:
var skinned = load("res://characters/skinned_player_model.gd").new() var skinned = load("res://characters/skinned_player_model.gd").new()
skinned.name = "SkinnedModel" skinned.name = "SkinnedModel"
skinned.model_path = "res://assets/characters/skins/miku_rigged_animated.glb" skinned.model_path = "res://assets/characters/skins/miku_rigged_animated.glb"
skinned.position = Vector3(0, 0.0, 0) # Model origin aligns with player origin skinned.position = Vector3(0, 0.0, 0)
skinned.first_person_mode = true # Hide body in FPS view, show only arms skinned.first_person_mode = true
player.add_child(skinned) player.add_child(skinned)
print("LevelRuntime: added SkinnedModel for local player")
else: else:
# Remote players: use procedural humanoid # Remote players: use procedural humanoid
var humanoid = load("res://characters/humanoid_model.gd").new() var humanoid = load("res://characters/humanoid_model.gd").new()