using FishNet.Object; using UnityEngine; namespace Ashwild.Network { /// /// Splits the player's visible body by viewpoint. The local owner's third-person body is /// moved onto a dedicated "local body" layer that his own first-person camera excludes from /// its culling mask, so he never sees his own torso/head clip through the view — while every /// remote client still renders that same body normally. The layer change is purely local and /// never travels over the network, so hiding your body from yourself does not hide it from /// anyone else. The Animator/NetworkAnimator are left untouched, so the third-person animation /// keeps replicating to other players even while the mesh is culled from the owner's own view. /// [DisallowMultipleComponent] public class PlayerBodyVisibility : NetworkBehaviour { #region Serialized Fields [Header("Body")] [Tooltip("Root of the third-person body mesh hierarchy (skinned mesh + bones).")] [SerializeField] private GameObject bodyRoot; [Header("Local hiding")] [Tooltip("Layer the owner's own body is moved to. The first-person camera must exclude this layer from its culling mask.")] [SerializeField] private string localBodyLayer = "LocalBody"; #endregion #region Network Lifecycle /// /// Once ownership is resolved, relayers the third-person body so the owning player's own /// camera stops rendering it. Remote copies return early and keep the body on its authored /// (visible) layer, which is why other players still see it. /// public override void OnStartClient() { base.OnStartClient(); if (!base.IsOwner) return; if (bodyRoot == null) { Debug.LogError($"[PlayerBodyVisibility] '{name}' has no bodyRoot assigned — cannot hide the local body.", this); return; } int layer = LayerMask.NameToLayer(localBodyLayer); if (layer < 0) { Debug.LogError($"[PlayerBodyVisibility] '{name}' — layer '{localBodyLayer}' does not exist. Add it in Project Settings ▸ Tags and Layers, then exclude it from the first-person camera's culling mask.", this); return; } SetLayerRecursively(bodyRoot, layer); } #endregion #region Internal Helpers /// /// Applies a layer to a GameObject and every descendant, since a skinned body spans many /// child renderers and bones that must all move together for the camera cull to hide it fully. /// private static void SetLayerRecursively(GameObject root, int layer) { root.layer = layer; foreach (Transform child in root.transform) SetLayerRecursively(child.gameObject, layer); } #endregion } }