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
Showing only changes of commit 73831cb418 - Show all commits
+36 -2
View File
@@ -56,7 +56,7 @@ func load_model(path: String) -> void:
add_child(scene)
print("SkinnedPlayerModel: scene added: %s" % scene.name)
# Model scale the Miku GLB is already exported at meter scale (~1.6m tall).
# Apply model scale (the Miku GLB is already in meters, ~1.6m tall)
# For models exported in centimeters (common from Mixamo/Blender), set scale_factor to 0.01.
scene.scale = Vector3(scale_factor, scale_factor, scale_factor)
print("SkinnedPlayerModel: applied scale %.4f" % scale_factor)
@@ -68,6 +68,18 @@ func load_model(path: String) -> void:
if mesh_instances.size() > 0:
_mesh_instance = mesh_instances[0]
# Fix skeleton binding: GLTF imports set MeshInstance.skeleton to ".."
# which should work for a direct parent, but in our case the MeshInstance
# is a child of Skeleton3D which is a child of MikuRig. The skeleton
# property needs to point to the Skeleton3D node for skin deformation.
# Set it explicitly to the skeleton's path relative to MeshInstance.
if _mesh_instance and skeleton:
# Manually compute relative path since get_relative_path_to is not
# exposed for MeshInstance3D in Godot 4.2.
# MeshInstance is child of Skeleton3D, so ".." points to Skeleton3D.
_mesh_instance.skeleton = NodePath("..")
print("SkinnedPlayerModel: fixed mesh skeleton binding -> '%s'" % _mesh_instance.skeleton)
if first_person_mode and _mesh_instance:
_setup_first_person()
@@ -88,6 +100,10 @@ func load_model(path: String) -> void:
# Find AnimationPlayer
animation_player = _find_animation_player(scene)
if animation_player:
# Ensure AnimationPlayer processes even if parent has process_mode disabled
animation_player.process_mode = Node.PROCESS_MODE_INHERIT
animation_player.active = true
print("SkinnedPlayerModel: AP process_mode=%d active=%s" % [animation_player.process_mode, animation_player.active])
var anim_list = animation_player.get_animation_list()
print("SkinnedPlayerModel: %d animations available:" % anim_list.size())
for anim_name in anim_list:
@@ -286,12 +302,20 @@ func _add_animation_to_player(ap: AnimationPlayer, anim_name: String, anim: Anim
lib.add_animation(anim_name, anim)
print("SkinnedPlayerModel: added '%s' (%d tracks)" % [anim_name, anim.get_track_count()])
func _update_skin() -> void:
## Force skeleton skin update by notifying the Skeleton3D that bones changed.
## GLTF runtime loaded models sometimes need this because the AnimationPlayer's
## internal bone pose updates don't always trigger the visual pipeline.
# Re-apply current bone poses to ensure dirty flags are set.
# This triggers NOTIFICATION_UPDATE_SKELETON on the Skeleton3D.
for i in range(skeleton.get_bone_count()):
skeleton.set_bone_pose(i, skeleton.get_bone_pose(i))
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
if not _mesh_instance:
return
print("SkinnedPlayerModel: setting up first-person mode")
# The mesh has multiple sub-meshes (body parts). We need to hide the ones
# that are above the chest (head, torso, skirt, etc.)
# For now, we hide the entire mesh and rely on the arms being separate
@@ -338,6 +362,7 @@ func play_animation(anim_name: String) -> void:
var _last_played: String = ""
var _missing_anim_warned: Array = []
var _skin_update_acc: float = 0.0
func _warn_missing(anim_name: String) -> void:
## Warn once per missing animation, not every frame.
@@ -359,6 +384,15 @@ func _process(delta: float) -> void:
elif _last_played == "":
print("SkinnedPlayerModel: NOT playing")
# Force skeleton skin update — GLTF runtime loaded models need explicit
# bone pose re-application to trigger MeshInstance vertex deformation.
# Throttled to ~30fps to avoid per-frame overhead.
if skeleton and animation_player.is_playing():
_skin_update_acc += delta
if _skin_update_acc > 0.033:
_skin_update_acc = 0.0
_update_skin()
# Get movement state from parent PlayerMovementController
var parent = get_parent()
if not parent: