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
+35 -11
View File
@@ -91,20 +91,36 @@ func _make_remote(pid: int) -> Node3D:
var root := Node3D.new()
root.name = "Remote_%d" % pid
var body := MeshInstance3D.new()
var capsule := CapsuleMesh.new()
capsule.radius = 0.35
capsule.height = 1.7
body.mesh = capsule
body.position = Vector3(0, 0.85, 0)
var mat := StandardMaterial3D.new()
mat.albedo_color = Color.from_hsv(fmod(float(pid) * 0.61803, 1.0), 0.6, 0.9)
body.material_override = mat
root.add_child(body)
# Other players get the assembled avatar too (everyone shares the default
# look for now -- per-player loadout replication is the follow-up), tinted
# by peer id so they're still tellable apart, plus a name tag.
var tint := Color.from_hsv(fmod(float(pid) * 0.61803, 1.0), 0.55, 0.95)
var avatars := get_node_or_null("/root/AvatarSystem")
var visual: Node3D = null
if avatars and avatars.has_method("build_current_avatar"):
visual = avatars.build_current_avatar()
if visual != null:
visual.position = Vector3(0, -0.85, 0) # feet -> ground (root is at hip height from server)
for mi in _all_mesh_instances(visual):
var m := StandardMaterial3D.new()
m.albedo_color = tint
mi.material_override = m
root.add_child(visual)
else:
var body := MeshInstance3D.new()
var capsule := CapsuleMesh.new()
capsule.radius = 0.35
capsule.height = 1.7
body.mesh = capsule
body.position = Vector3(0, 0.85, 0)
var mat := StandardMaterial3D.new()
mat.albedo_color = tint
body.material_override = mat
root.add_child(body)
var tag := Label3D.new()
tag.text = "Player %d" % pid
tag.position = Vector3(0, 2.0, 0)
tag.position = Vector3(0, 2.1, 0)
tag.billboard = BaseMaterial3D.BILLBOARD_ENABLED
tag.no_depth_test = true
tag.pixel_size = 0.004
@@ -113,6 +129,14 @@ func _make_remote(pid: int) -> Node3D:
return root
func _all_mesh_instances(node: Node, out: Array = []) -> Array:
if node is MeshInstance3D:
out.append(node)
for c in node.get_children():
_all_mesh_instances(c, out)
return out
func _on_server_lost() -> void:
_networked = false
_show_status("Disconnected from the server. Returning to worlds…")