fix: third-person camera orbits player facing + robust animation handling
Third-person camera: - Camera offset now follows player's forward direction using basis.z - Smoothly orbits behind player when they turn - Looks at player's head area (y+1.5) SkinnedPlayerModel: - Creates AnimationPlayer programmatically if GLB doesn't have one - Copies animations from armature's animation_data to the player - Handles both direct actions and NLA tracks - More detailed debug output for scene tree and animation state
This commit is contained in:
@@ -15,7 +15,6 @@ func _ready() -> void:
|
||||
load_model(model_path)
|
||||
|
||||
func load_model(path: String) -> void:
|
||||
# Clear existing children
|
||||
for child in get_children():
|
||||
child.queue_free()
|
||||
|
||||
@@ -32,36 +31,82 @@ func load_model(path: String) -> void:
|
||||
add_child(scene)
|
||||
|
||||
# Debug: print the full scene tree
|
||||
print("SkinnedPlayerModel: scene tree after instantiation:")
|
||||
print("SkinnedPlayerModel: scene tree:")
|
||||
_print_tree(scene, 0)
|
||||
|
||||
# Find animation player — search entire tree including the scene root itself
|
||||
animation_player = find_animation_player(self)
|
||||
if not animation_player:
|
||||
animation_player = find_animation_player(scene)
|
||||
|
||||
# Find skeleton
|
||||
skeleton = find_skeleton(self)
|
||||
if not skeleton:
|
||||
skeleton = find_skeleton(scene)
|
||||
|
||||
# Find existing AnimationPlayer in the GLB scene
|
||||
animation_player = find_animation_player_recursive(scene)
|
||||
|
||||
# If no AnimationPlayer found, create one and add it to the armature
|
||||
if not animation_player:
|
||||
print("SkinnedPlayerModel: no AnimationPlayer in GLB, creating one...")
|
||||
animation_player = AnimationPlayer.new()
|
||||
animation_player.name = "AnimationPlayer"
|
||||
|
||||
# Add to the skeleton/armature node if found, otherwise to self
|
||||
if skeleton:
|
||||
skeleton.add_child(animation_player)
|
||||
print("SkinnedPlayerModel: added AnimationPlayer to skeleton")
|
||||
else:
|
||||
add_child(animation_player)
|
||||
print("SkinnedPlayerModel: added AnimationPlayer to self")
|
||||
|
||||
# Try to copy animations from the armature's animation_data
|
||||
if skeleton and skeleton.animation_data:
|
||||
var src_data = skeleton.animation_data
|
||||
# Create animation library
|
||||
var anim_lib = AnimationLibrary.new()
|
||||
|
||||
# Copy the action from the armature
|
||||
if src_data.action:
|
||||
var anim = src_data.action.copy()
|
||||
anim_lib.add_animation(anim.name, anim)
|
||||
print(f"SkinnedPlayerModel: copied animation '{anim.name}' ({anim.length:.2f}s)")
|
||||
|
||||
# Also check NLA tracks
|
||||
if src_data.nla_tracks:
|
||||
for track in src_data.nla_tracks:
|
||||
for strip in track.strips:
|
||||
if strip.action:
|
||||
var anim = strip.action.copy()
|
||||
anim_lib.add_animation(anim.name, anim)
|
||||
print(f"SkinnedPlayerModel: copied NLA animation '{anim.name}'")
|
||||
|
||||
animation_player.add_animation_library("", anim_lib)
|
||||
else:
|
||||
print("SkinnedPlayerModel: WARNING - skeleton has no animation_data")
|
||||
|
||||
# List available animations
|
||||
if animation_player:
|
||||
var anim_list = animation_player.get_animation_list()
|
||||
print("SkinnedPlayerModel: found AnimationPlayer with %d animations:" % anim_list.size())
|
||||
print("SkinnedPlayerModel: %d animations available:" % anim_list.size())
|
||||
for anim in anim_list:
|
||||
print(" - %s (length: %.2f)" % [anim, animation_player.get_animation(anim).length])
|
||||
var a = animation_player.get_animation(anim)
|
||||
print(f" - {anim} ({a.length:.2f}s, loop={a.loop_mode})")
|
||||
|
||||
# Auto-play idle
|
||||
if 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 first anim: %s" % anim_list[0])
|
||||
print("SkinnedPlayerModel: playing '%s'" % anim_list[0])
|
||||
else:
|
||||
print("SkinnedPlayerModel: WARNING - no AnimationPlayer found in scene tree")
|
||||
print("SkinnedPlayerModel: ERROR - no AnimationPlayer available")
|
||||
|
||||
func _print_tree(node: Node, depth: int) -> void:
|
||||
var indent = " ".repeat(depth)
|
||||
print("%s%s (%s)" % [indent, node.name, node.get_class()])
|
||||
var extra = ""
|
||||
if node is AnimationPlayer:
|
||||
extra = " [ANIMATION PLAYER]"
|
||||
elif node is Skeleton3D:
|
||||
extra = " [SKELETON]"
|
||||
print("%s%s (%s)%s" % [indent, node.name, node.get_class(), extra])
|
||||
for child in node.get_children():
|
||||
_print_tree(child, depth + 1)
|
||||
|
||||
@@ -74,11 +119,11 @@ func find_skeleton(node: Node) -> Skeleton3D:
|
||||
return result
|
||||
return null
|
||||
|
||||
func find_animation_player(node: Node) -> AnimationPlayer:
|
||||
func find_animation_player_recursive(node: Node) -> AnimationPlayer:
|
||||
if node is AnimationPlayer:
|
||||
return node
|
||||
for child in node.get_children():
|
||||
var result = find_animation_player(child)
|
||||
var result = find_animation_player_recursive(child)
|
||||
if result:
|
||||
return result
|
||||
return null
|
||||
@@ -93,28 +138,26 @@ func _process(delta: float) -> void:
|
||||
if not animation_player:
|
||||
return
|
||||
|
||||
# Debug: print animation state occasionally
|
||||
# Debug output every 2 seconds
|
||||
_anim_debug_timer += delta
|
||||
if _anim_debug_timer > 2.0:
|
||||
_anim_debug_timer = 0.0
|
||||
if animation_player.is_playing():
|
||||
print("SkinnedPlayerModel: playing '%s' (pos: %.2f)" % [animation_player.current_animation, animation_player.current_animation_position])
|
||||
else:
|
||||
print("SkinnedPlayerModel: NOT playing any animation")
|
||||
print("SkinnedPlayerModel: NOT playing")
|
||||
|
||||
# Get movement state from the state machine
|
||||
# Get movement state
|
||||
var sm = get_parent().get_node_or_null("MovementStateMachine")
|
||||
if not sm:
|
||||
return
|
||||
|
||||
var state = sm.current_state
|
||||
# Calculate speed from player's horizontal velocity
|
||||
var speed = 0.0
|
||||
if sm.player:
|
||||
var vel = sm.player.velocity
|
||||
speed = Vector2(vel.x, vel.z).length()
|
||||
|
||||
# Only change animation if it's different from current
|
||||
var target_anim = "Idle"
|
||||
match state:
|
||||
"ground", "idle":
|
||||
|
||||
@@ -748,11 +748,18 @@ func _process(_delta: float) -> void:
|
||||
if not _player or not is_instance_valid(_player):
|
||||
return
|
||||
|
||||
# Third person camera follow
|
||||
# Third person camera follow — orbit behind player's facing direction
|
||||
if _third_person and _third_person_camera and _third_person_camera.current:
|
||||
var target_pos = _player.global_position + Vector3(0, 2.0, -4.0)
|
||||
_third_person_camera.global_position = _third_person_camera.global_position.lerp(target_pos, 0.1)
|
||||
_third_person_camera.look_at(_player.global_position + Vector3(0, 1.0, 0))
|
||||
# Get the player's forward direction (on XZ plane)
|
||||
var forward = -_player.global_transform.basis.z
|
||||
forward.y = 0
|
||||
forward = forward.normalized()
|
||||
# Position camera behind and above player
|
||||
var cam_offset = forward * -4.0 + Vector3.UP * 2.5
|
||||
var target_pos = _player.global_position + cam_offset
|
||||
_third_person_camera.global_position = _third_person_camera.global_position.lerp(target_pos, 0.12)
|
||||
# Look at player's head area
|
||||
_third_person_camera.look_at(_player.global_position + Vector3.UP * 1.5)
|
||||
|
||||
if _debug_ui_panel:
|
||||
_debug_ui_panel.visible = SettingsManager.show_debug_ui
|
||||
|
||||
Reference in New Issue
Block a user