using Steamworks; using UnityEngine; namespace MegaKoop.Game.Networking { [DisallowMultipleComponent] public class SteamCharacterNetworkBridge : MonoBehaviour { [Header("References")] [SerializeField] private ThirdPersonCharacterController characterController; [SerializeField] private Animator animator; [SerializeField] private NetworkIdentity identity; [SerializeField] private Transform rootTransform; [SerializeField] private NetworkCharacterInputProxy networkInputProxy; [Header("Settings")] [SerializeField] private float transformBroadcastInterval = 0.05f; [SerializeField] private float remoteLerpSpeed = 12f; [SerializeField] private ulong ownerSteamId; [SerializeField] private bool autoAssignOwnerToLocalPlayer = true; private SteamCoopNetworkManager networkManager; private float broadcastTimer; private bool isRegistered; private bool isLocalPlayer; private bool isAuthority; private Vector3 remoteTargetPosition; private Quaternion remoteTargetRotation; private Vector3 remoteTargetVelocity; private bool haveRemoteState; private bool localOverrideSet; private int expectedNetworkId; public void AssignOwner(ulong steamId, bool localPlayer) { ownerSteamId = steamId; isLocalPlayer = localPlayer; localOverrideSet = true; CaptureExpectedNetworkId(); UpdateAuthority(); ConfigureController(); } public bool IsLocalPlayer => isLocalPlayer; public bool IsAuthority => isAuthority; public ulong OwnerSteamId => ownerSteamId; private void Awake() { if (characterController == null) { characterController = GetComponent(); } if (identity == null) { identity = GetComponent(); } if (rootTransform == null) { rootTransform = transform; } if (networkInputProxy == null) { networkInputProxy = GetComponent(); if (networkInputProxy == null) { networkInputProxy = gameObject.AddComponent(); networkInputProxy.hideFlags = HideFlags.HideInInspector; } } if (animator == null) { animator = GetComponent(); if (animator != null) { animator.cullingMode = AnimatorCullingMode.AlwaysAnimate; } } remoteTargetPosition = rootTransform.position; remoteTargetRotation = rootTransform.rotation; } private void Start() { networkManager = SteamCoopNetworkManager.Instance; CaptureExpectedNetworkId(); UpdateAuthority(); ConfigureController(); } private void OnEnable() { networkManager = SteamCoopNetworkManager.Instance; CaptureExpectedNetworkId(); RegisterHandlers(); } private void OnDisable() { UnregisterHandlers(); } private void Update() { if (networkManager == null) { networkManager = SteamCoopNetworkManager.Instance; if (networkManager != null) { RegisterHandlers(); } } if (networkManager == null) { return; } if (isAuthority) { broadcastTimer -= Time.deltaTime; if (broadcastTimer <= 0f) { BroadcastTransform(); broadcastTimer = transformBroadcastInterval; } } else { if (haveRemoteState) { rootTransform.position = Vector3.Lerp(rootTransform.position, remoteTargetPosition, remoteLerpSpeed * Time.deltaTime); rootTransform.rotation = Quaternion.Slerp(rootTransform.rotation, remoteTargetRotation, remoteLerpSpeed * Time.deltaTime); } } } private void RegisterHandlers() { if (networkManager == null || isRegistered) { return; } networkManager.RegisterHandler(NetworkMessageType.PlayerInput, HandlePlayerInputMessage); networkManager.RegisterHandler(NetworkMessageType.CharacterTransform, HandleCharacterTransformMessage); networkManager.RegisterHandler(NetworkMessageType.CharacterAnim, HandleCharacterAnimMessage); isRegistered = true; } private void UnregisterHandlers() { if (networkManager == null || !isRegistered) { return; } networkManager.UnregisterHandler(NetworkMessageType.PlayerInput, HandlePlayerInputMessage); networkManager.UnregisterHandler(NetworkMessageType.CharacterTransform, HandleCharacterTransformMessage); networkManager.UnregisterHandler(NetworkMessageType.CharacterAnim, HandleCharacterAnimMessage); isRegistered = false; } private void UpdateAuthority() { if (networkManager == null) { networkManager = SteamCoopNetworkManager.Instance; } ulong localSteamId = SteamBootstrap.IsInitialized ? SteamUser.GetSteamID().m_SteamID : 0UL; if (!localOverrideSet && autoAssignOwnerToLocalPlayer && ownerSteamId == 0 && localSteamId != 0) { ownerSteamId = localSteamId; } if (!localOverrideSet) { isLocalPlayer = ownerSteamId != 0 && ownerSteamId == localSteamId; } isAuthority = isLocalPlayer; // Each player has authority over their own character. } private void ConfigureController() { if (characterController == null) { return; } if (isAuthority) { characterController.enabled = true; characterController.SetInputSource(isLocalPlayer ? null : networkInputProxy); var unityController = characterController.GetComponent(); if (unityController != null) { unityController.enabled = true; unityController.detectCollisions = true; unityController.enableOverlapRecovery = true; } } else { characterController.enabled = false; characterController.SetInputSource(null); var unityController = characterController.GetComponent(); if (unityController != null) { unityController.enabled = true; unityController.detectCollisions = true; unityController.enableOverlapRecovery = false; } } } private void BroadcastTransform() { if (identity == null) { return; } if (identity.NetworkId == 0) { return; } var unityController = GetComponent(); Vector3 velocity = unityController != null ? unityController.velocity : Vector3.zero; SteamCharacterStateCache.ReportLocalState(identity.NetworkId, rootTransform.position, rootTransform.rotation, velocity); var message = new CharacterTransformMessage(identity.NetworkId, rootTransform.position, rootTransform.rotation, velocity); byte[] payload = CharacterTransformMessage.Serialize(message); networkManager.SendToAll(NetworkMessageType.CharacterTransform, payload, EP2PSend.k_EP2PSendUnreliableNoDelay); BroadcastAnimatorParameters(); } private void BroadcastAnimatorParameters() { if (networkManager == null || identity == null || animator == null) { return; } if (identity.NetworkId == 0) { return; } float moveX = animator.GetFloat("MoveX"); float moveZ = animator.GetFloat("MoveZ"); float speed = animator.GetFloat("Speed"); float moveSpeedNorm = animator.GetFloat("MoveSpeedNormalized"); bool isGround = animator.GetBool("IsGrounded"); bool isCrouch = animator.GetBool("IsCrouching"); bool isDeadFlag = animator.GetBool("IsDead"); bool isJump = animator.GetBool("IsJumping"); var animMsg = new CharacterAnimMessage( identity.NetworkId, moveX, moveZ, speed, moveSpeedNorm, isGround, isCrouch, isDeadFlag, isJump); byte[] animPayload = CharacterAnimMessage.Serialize(animMsg); networkManager.SendToAll(NetworkMessageType.CharacterAnim, animPayload, EP2PSend.k_EP2PSendUnreliableNoDelay); } private void HandlePlayerInputMessage(NetworkMessage message) { if (isAuthority) { return; } PlayerInputMessage inputMessage = PlayerInputMessage.Deserialize(message.Payload); if (inputMessage.NetworkId != identity.NetworkId) { return; } networkInputProxy.SetInput(inputMessage.MoveInput, inputMessage.JumpPressed); } private void HandleCharacterTransformMessage(NetworkMessage message) { if (isAuthority) { return; } CharacterTransformMessage transformMessage = CharacterTransformMessage.Deserialize(message.Payload); if (!EnsureMatchingNetworkId(transformMessage.NetworkId, message.Sender)) { return; } remoteTargetPosition = transformMessage.Position; remoteTargetRotation = transformMessage.Rotation; remoteTargetVelocity = transformMessage.Velocity; haveRemoteState = true; } private void HandleCharacterAnimMessage(NetworkMessage message) { if (isAuthority) { return; } CharacterAnimMessage anim = CharacterAnimMessage.Deserialize(message.Payload); if (!EnsureMatchingNetworkId(anim.NetworkId, message.Sender)) { return; } if (animator == null) { animator = GetComponent(); if (animator == null) { return; } } animator.SetFloat("MoveX", anim.MoveX); animator.SetFloat("MoveZ", anim.MoveZ); animator.SetFloat("Speed", anim.Speed); animator.SetFloat("MoveSpeedNormalized", anim.MoveSpeedNormalized); animator.SetBool("IsGrounded", anim.IsGrounded); animator.SetBool("IsCrouching", anim.IsCrouching); animator.SetBool("IsDead", anim.IsDead); animator.SetBool("IsJumping", anim.IsJumping); } private void CaptureExpectedNetworkId() { if (identity == null) { identity = GetComponent(); } if (identity == null) { return; } if (identity.NetworkId != 0) { expectedNetworkId = identity.NetworkId; if (ownerSteamId != 0) { var mapped = NetworkIdRegistry.GetNetworkIdForSteamId(ownerSteamId); if (mapped != expectedNetworkId) { NetworkIdRegistry.MapSteamIdToNetworkId(ownerSteamId, expectedNetworkId); } } } } private bool EnsureMatchingNetworkId(int incomingId, ulong sender) { if (identity == null) { return false; } if (incomingId == 0) { return false; } int currentId = identity.NetworkId; if (currentId == incomingId) { return true; } if (currentId != 0) { return false; } if (ownerSteamId != 0 && sender != ownerSteamId) { return false; } int mappedId = ownerSteamId != 0 ? NetworkIdRegistry.GetNetworkIdForSteamId(ownerSteamId) : expectedNetworkId; if (mappedId != 0) { identity.SetNetworkId(mappedId); if (identity.NetworkId == mappedId) { expectedNetworkId = mappedId; return mappedId == incomingId; } return false; } if (!NetworkIdAllocator.IsPlayerId(incomingId)) { return false; } identity.SetNetworkId(incomingId); if (identity.NetworkId != incomingId) { return false; } expectedNetworkId = incomingId; if (ownerSteamId != 0) { NetworkIdRegistry.MapSteamIdToNetworkId(ownerSteamId, incomingId); } return true; } public void SendLocalInput(Vector2 moveInput, bool jump) { if (networkManager == null || identity == null || !isAuthority) { return; } if (!networkManager.IsConnected) { return; } if (identity.NetworkId == 0) { return; } networkManager.SendToAll(NetworkMessageType.PlayerInput, PlayerInputMessage.Serialize(new PlayerInputMessage(identity.NetworkId, moveInput, jump)), EP2PSend.k_EP2PSendUnreliableNoDelay); } } }