Avatars actually show; camera stops hugging the player
checkpoint / checkpoint (push) Successful in 1m40s

- SpringArm3D was colliding with the player's own capsule (same layer) ->
  camera yanked up against the head. add_excluded_object(self) in _ready +
  gentler default pitch, shorter arm, lower pivot.
- AvatarSystem.build_current_avatar() is now self-sufficient: loads the
  catalog itself and falls back to a bare default loadout if login didn't
  prime one. So the in-world player always shows an avatar, never the
  capsule, as long as the assets are packed (they are now).
- New AvatarSystem.assemble_avatar(): builds a standalone avatar with a
  throwaway loader, touching no shared state -- so it's safe to call once
  for the local player AND once per remote peer. (load_avatar()'s shared
  loader reset()s and freed the previous avatar on every call.)
- game.gd: remote players render as the assembled avatar too (shared default
  look, tinted per-peer, name tag) instead of a plain capsule.

Client-side -- rebuild the exe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
This commit is contained in:
2026-09-05 23:52:10 +00:00
parent 54bc763dff
commit 80f24bfd01
4 changed files with 98 additions and 22 deletions
+12 -7
View File
@@ -25,7 +25,7 @@ const MOUSE_SENSITIVITY: float = 0.003
# ---------------------------------------------------------------------------
var _input_direction := Vector2.ZERO
var _camera_pitch: float = -0.25
var _camera_pitch: float = -0.12
## Vertical offset applied to the assembled avatar so its feet (origin y=0)
## line up with the bottom of the player's collision capsule.
@@ -70,6 +70,12 @@ func _ready() -> void:
push_warning("player.gd: no PlayerSpawn marker found; spawning at origin")
global_position = Vector3(0, 1.0, 0)
# The spring arm must not collide with the player's own capsule (they're
# on the same layer), or the camera gets yanked right up against the
# player's head.
var arm := $CameraPivot/SpringArm3D as SpringArm3D
arm.add_excluded_object(self)
# Start the camera arm tilted down a little so the avatar is framed.
_camera_pivot.rotation.x = _camera_pitch
@@ -81,18 +87,17 @@ func _ready() -> void:
_apply_avatar()
## Replace the placeholder capsule with the player's assembled avatar, if the
## AvatarSystem has a loadout primed (set at login by main_menu.gd). Falls back
## to the capsule silently when there's no avatar to show -- e.g. running this
## scene directly in a test, or an offline/failed catalog load.
## TODO(PC3): a third-person camera so the local player actually sees this;
## for now it's mainly for other players (Phase 2) and looking down.
## Show the player's assembled avatar and hide the placeholder capsule.
## AvatarSystem.build_current_avatar() is self-sufficient now (loads the
## catalog + falls back to a default loadout), so this only no-ops if the
## avatar assets themselves are missing.
func _apply_avatar() -> void:
var avatars := get_node_or_null("/root/AvatarSystem")
if avatars == null or not avatars.has_method("build_current_avatar"):
return
var avatar: Node3D = avatars.build_current_avatar()
if avatar == null:
push_warning("player.gd: no avatar to show; keeping the placeholder capsule")
return
avatar.position = Vector3(0.0, AVATAR_FEET_OFFSET, 0.0)
add_child(avatar)