Sync per-pose index and locomotion state across the network

Add PlayerAnimationSync NetworkBehaviour with owner-authoritative
NetworkVariables for pose index and locomotion category (idle/walk/run/fall).
Remote avatars switch their PlayerPoseController into network-controlled mode,
skipping local input, sprint reset, capsule resize and camera framing, and
take pose index plus locomotion externally. Disable pose persistence on
network-driven remotes so foreign avatars can't overwrite the local save.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 4.8 committed 2026-06-27 14:44:11 +02:00
1 parent 4fa443f630
commit 7f2f4e5999
5 files changed
+186 -29

No files matched your search

+70
View File
@@ -0,0 +1,70 @@
using Unity.Netcode;
using UnityEngine;
/// <summary>
/// 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
/// <see cref="PlayerPoseController"/> 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.
/// </summary>
[RequireComponent(typeof(PlayerPoseController))]
[DisallowMultipleComponent]
[DefaultExecutionOrder(200)]
public class PlayerAnimationSync : NetworkBehaviour
{
private readonly NetworkVariable<int> _poseIndex =
new(0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
// Locomotion category stored as a byte (PlayerPoseController.Locomotion has only a handful of values).
private readonly NetworkVariable<byte> _locomotion =
new(0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
private PlayerPoseController _pose;
void Awake() => _pose = GetComponent<PlayerPoseController>();
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);
}