using Unity.Netcode; using UnityEngine; /// /// Replicates the pose system's animation state across the network. The owner publishes its selected /// pose index and resolved locomotion category (idle / walk / run / fall); remote avatars switch their /// to network-driven mode and play the matching clip, so a remote /// player shows the same pose and walk/run/fall animation the owner sees — not just a sliding idle. /// /// Position/rotation come from the NetworkTransform; this only carries the small animation state. Both /// values are owner-authoritative and change rarely (a pose cycle, a locomotion transition), so the /// NetworkVariables send a delta only when the value actually changes. /// /// Runs after the PlayerPoseController (execution order 100) so the owner reads this frame's resolved /// state; remote avatars apply via OnValueChanged, independent of frame order. /// [RequireComponent(typeof(PlayerPoseController))] [DisallowMultipleComponent] [DefaultExecutionOrder(200)] public class PlayerAnimationSync : NetworkBehaviour { private readonly NetworkVariable _poseIndex = new(0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); // Locomotion category stored as a byte (PlayerPoseController.Locomotion has only a handful of values). private readonly NetworkVariable _locomotion = new(0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); private PlayerPoseController _pose; void Awake() => _pose = GetComponent(); public override void OnNetworkSpawn() { if (IsOwner) return; // Remote avatar: stop reading local input/physics and drive the pose from replicated state. _pose.EnableNetworkDrive(); _poseIndex.OnValueChanged += OnPoseIndexChanged; _locomotion.OnValueChanged += OnLocomotionChanged; ApplyRemote(); // spawn-time values are already replicated } public override void OnNetworkDespawn() { _poseIndex.OnValueChanged -= OnPoseIndexChanged; _locomotion.OnValueChanged -= OnLocomotionChanged; } void Update() { if (!IsOwner || !_pose) return; // Publish the owner's resolved animation state. Assigning the same value is a no-op for // replication (the NetworkVariable dirties only on an actual change), but guard anyway to keep // intent clear. int index = _pose.CurrentIndex; byte loco = (byte)_pose.CurrentLocomotion; if (_poseIndex.Value != index) _poseIndex.Value = index; if (_locomotion.Value != loco) _locomotion.Value = loco; } private void OnPoseIndexChanged(int previous, int current) => ApplyRemote(); private void OnLocomotionChanged(byte previous, byte current) => ApplyRemote(); private void ApplyRemote() => _pose.SetNetworkState(_poseIndex.Value, (PlayerPoseController.Locomotion)_locomotion.Value); }