using System.Linq; using System.Collections.Generic; using UnityEngine; using MLAPI.Messaging; namespace MLAPI.Prototyping { /// /// A prototype component for syncing transforms /// [AddComponentMenu("MLAPI/NetworkTransform")] public class NetworkTransform : NetworkBehaviour { internal class ClientSendInfo { public float LastSent; public Vector3? LastMissedPosition; public Quaternion? LastMissedRotation; } /// /// The base amount of sends per seconds to use when range is disabled /// [Range(0, 120)] public float FixedSendsPerSecond = 20f; /// /// Is the sends per second assumed to be the same across all instances /// [Tooltip("This assumes that the SendsPerSecond is synced across clients")] public bool AssumeSyncedSends = true; /// /// Enable interpolation /// [Tooltip("This requires AssumeSyncedSends to be true")] public bool InterpolatePosition = true; /// /// The distance before snaping to the position /// [Tooltip("The transform will snap if the distance is greater than this distance")] public float SnapDistance = 10f; /// /// Should the server interpolate /// public bool InterpolateServer = true; /// /// The min meters to move before a send is sent /// public float MinMeters = 0.15f; /// /// The min degrees to rotate before a send it sent /// public float MinDegrees = 1.5f; /// /// Enables extrapolation /// public bool ExtrapolatePosition = false; /// /// The maximum amount of expected send rates to extrapolate over when awaiting new packets. /// A higher value will result in continued extrapolation after an object has stopped moving /// public float MaxSendsToExtrapolate = 5; /// /// The channel to send the data on /// [Tooltip("The channel to send the data on. Uses the default channel if left unspecified")] public string Channel = null; private float m_LerpTime; private Vector3 m_LerpStartPos; private Quaternion m_LerpStartRot; private Vector3 m_LerpEndPos; private Quaternion m_LerpEndRot; private float m_LastSendTime; private Vector3 m_LastSentPos; private Quaternion m_LastSentRot; private float m_LastReceiveTime; /// /// Enables range based send rate /// public bool EnableRange; /// /// Checks for missed sends without provocation. Provocation being a client inside it's normal SendRate /// public bool EnableNonProvokedResendChecks; /// /// The curve to use to calculate the send rate /// public AnimationCurve DistanceSendrate = AnimationCurve.Constant(0, 500, 20); private readonly Dictionary m_ClientSendInfo = new Dictionary(); /// /// The delegate used to check if a move is valid /// /// The client id the move is being validated for /// The previous position /// The new requested position /// Returns Whether or not the move is valid public delegate bool MoveValidationDelegate(ulong clientId, Vector3 oldPos, Vector3 newPos); /// /// If set, moves will only be accepted if the custom delegate returns true /// public MoveValidationDelegate IsMoveValidDelegate = null; private void OnValidate() { if (!AssumeSyncedSends && InterpolatePosition) InterpolatePosition = false; if (InterpolateServer && !InterpolatePosition) InterpolateServer = false; if (MinDegrees < 0) MinDegrees = 0; if (MinMeters < 0) MinMeters = 0; if (EnableNonProvokedResendChecks && !EnableRange) EnableNonProvokedResendChecks = false; } private float GetTimeForLerp(Vector3 pos1, Vector3 pos2) { return 1f / DistanceSendrate.Evaluate(Vector3.Distance(pos1, pos2)); } /// /// Registers message handlers /// public override void NetworkStart() { m_LastSentRot = transform.rotation; m_LastSentPos = transform.position; m_LerpStartPos = transform.position; m_LerpStartRot = transform.rotation; m_LerpEndPos = transform.position; m_LerpEndRot = transform.rotation; } private void Update() { if (IsOwner) { if (NetworkManager.Singleton.NetworkTime - m_LastSendTime >= (1f / FixedSendsPerSecond) && (Vector3.Distance(transform.position, m_LastSentPos) > MinMeters || Quaternion.Angle(transform.rotation, m_LastSentRot) > MinDegrees)) { m_LastSendTime = NetworkManager.Singleton.NetworkTime; m_LastSentPos = transform.position; m_LastSentRot = transform.rotation; if (IsServer) { ApplyTransformClientRpc(transform.position, transform.rotation.eulerAngles, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = NetworkManager.Singleton.ConnectedClientsList.Where(c => c.ClientId != OwnerClientId).Select(c => c.ClientId).ToArray() } }); } else { SubmitTransformServerRpc(transform.position, transform.rotation.eulerAngles); } } } else { //If we are server and interpolation is turned on for server OR we are not server and interpolation is turned on if ((IsServer && InterpolateServer && InterpolatePosition) || (!IsServer && InterpolatePosition)) { if (Vector3.Distance(transform.position, m_LerpEndPos) > SnapDistance) { //Snap, set T to 1 (100% of the lerp) m_LerpTime = 1f; } float sendDelay = (IsServer || !EnableRange || !AssumeSyncedSends || NetworkManager.Singleton.ConnectedClients[NetworkManager.Singleton.LocalClientId].PlayerObject == null) ? (1f / FixedSendsPerSecond) : GetTimeForLerp(transform.position, NetworkManager.Singleton.ConnectedClients[NetworkManager.Singleton.LocalClientId].PlayerObject.transform.position); m_LerpTime += Time.unscaledDeltaTime / sendDelay; if (ExtrapolatePosition && Time.unscaledTime - m_LastReceiveTime < sendDelay * MaxSendsToExtrapolate) transform.position = Vector3.LerpUnclamped(m_LerpStartPos, m_LerpEndPos, m_LerpTime); else transform.position = Vector3.Lerp(m_LerpStartPos, m_LerpEndPos, m_LerpTime); if (ExtrapolatePosition && Time.unscaledTime - m_LastReceiveTime < sendDelay * MaxSendsToExtrapolate) transform.rotation = Quaternion.SlerpUnclamped(m_LerpStartRot, m_LerpEndRot, m_LerpTime); else transform.rotation = Quaternion.Slerp(m_LerpStartRot, m_LerpEndRot, m_LerpTime); } } if (IsServer && EnableRange && EnableNonProvokedResendChecks) CheckForMissedSends(); } [ClientRpc] private void ApplyTransformClientRpc(Vector3 position, Vector3 eulerAngles, ClientRpcParams rpcParams = default) { if (enabled) { ApplyTransformInternal(position, Quaternion.Euler(eulerAngles)); } } private void ApplyTransformInternal(Vector3 position, Quaternion rotation) { if (!enabled) return; if (InterpolatePosition && (!IsServer || InterpolateServer)) { m_LastReceiveTime = Time.unscaledTime; m_LerpStartPos = transform.position; m_LerpStartRot = transform.rotation; m_LerpEndPos = position; m_LerpEndRot = rotation; m_LerpTime = 0; } else { transform.position = position; transform.rotation = rotation; } } [ServerRpc] private void SubmitTransformServerRpc(Vector3 position, Vector3 eulerAngles, ServerRpcParams rpcParams = default) { if (!enabled) return; if (IsMoveValidDelegate != null && !IsMoveValidDelegate(rpcParams.Receive.SenderClientId, m_LerpEndPos, position)) { //Invalid move! //TODO: Add rubber band (just a message telling them to go back) return; } if (!IsClient) { // Dedicated server ApplyTransformInternal(position, Quaternion.Euler(eulerAngles)); } if (EnableRange) { for (int i = 0; i < NetworkManager.Singleton.ConnectedClientsList.Count; i++) { if (!m_ClientSendInfo.ContainsKey(NetworkManager.Singleton.ConnectedClientsList[i].ClientId)) { m_ClientSendInfo.Add(NetworkManager.Singleton.ConnectedClientsList[i].ClientId, new ClientSendInfo() { LastMissedPosition = null, LastMissedRotation = null, LastSent = 0 }); } ClientSendInfo info = m_ClientSendInfo[NetworkManager.Singleton.ConnectedClientsList[i].ClientId]; Vector3? receiverPosition = NetworkManager.Singleton.ConnectedClientsList[i].PlayerObject == null ? null : new Vector3?(NetworkManager.Singleton.ConnectedClientsList[i].PlayerObject.transform.position); Vector3? senderPosition = NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject == null ? null : new Vector3?(NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject.transform.position); if ((receiverPosition == null || senderPosition == null && NetworkManager.Singleton.NetworkTime - info.LastSent >= (1f / FixedSendsPerSecond)) || NetworkManager.Singleton.NetworkTime - info.LastSent >= GetTimeForLerp(receiverPosition.Value, senderPosition.Value)) { info.LastSent = NetworkManager.Singleton.NetworkTime; info.LastMissedPosition = null; info.LastMissedRotation = null; ApplyTransformClientRpc(position, eulerAngles, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = new[] { NetworkManager.Singleton.ConnectedClientsList[i].ClientId } } }); } else { info.LastMissedPosition = position; info.LastMissedRotation = Quaternion.Euler(eulerAngles); } } } else { ApplyTransformClientRpc(position, eulerAngles, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = NetworkManager.Singleton.ConnectedClientsList.Where(c => c.ClientId != OwnerClientId).Select(c => c.ClientId).ToArray() } }); } } private void CheckForMissedSends() { for (int i = 0; i < NetworkManager.Singleton.ConnectedClientsList.Count; i++) { if (!m_ClientSendInfo.ContainsKey(NetworkManager.Singleton.ConnectedClientsList[i].ClientId)) { m_ClientSendInfo.Add(NetworkManager.Singleton.ConnectedClientsList[i].ClientId, new ClientSendInfo() { LastMissedPosition = null, LastMissedRotation = null, LastSent = 0 }); } ClientSendInfo info = m_ClientSendInfo[NetworkManager.Singleton.ConnectedClientsList[i].ClientId]; Vector3? receiverPosition = NetworkManager.Singleton.ConnectedClientsList[i].PlayerObject == null ? null : new Vector3?(NetworkManager.Singleton.ConnectedClientsList[i].PlayerObject.transform.position); Vector3? senderPosition = NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject == null ? null : new Vector3?(NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject.transform.position); if ((receiverPosition == null || senderPosition == null && NetworkManager.Singleton.NetworkTime - info.LastSent >= (1f / FixedSendsPerSecond)) || NetworkManager.Singleton.NetworkTime - info.LastSent >= GetTimeForLerp(receiverPosition.Value, senderPosition.Value)) { /* why is this??? ->*/ Vector3? pos = NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject == null ? null : new Vector3?(NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject.transform.position); /* why is this??? ->*/ Vector3? rot = NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject == null ? null : new Vector3?(NetworkManager.Singleton.ConnectedClients[OwnerClientId].PlayerObject.transform.rotation.eulerAngles); if (info.LastMissedPosition != null && info.LastMissedRotation != null) { info.LastSent = NetworkManager.Singleton.NetworkTime; ApplyTransformClientRpc(info.LastMissedPosition.Value, info.LastMissedRotation.Value.eulerAngles, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = new[] { NetworkManager.Singleton.ConnectedClientsList[i].ClientId } } }); info.LastMissedPosition = null; info.LastMissedRotation = null; } } } } /// /// Teleports the transform to the given position and rotation /// /// The position to teleport to /// The rotation to teleport to public void Teleport(Vector3 position, Quaternion rotation) { if (InterpolateServer && IsServer || IsClient) { m_LerpStartPos = position; m_LerpStartRot = rotation; m_LerpEndPos = position; m_LerpEndRot = rotation; m_LerpTime = 0; } } } }