using System; using System.Collections.Generic; using Unity.Netcode; using UnityEngine; //用插值的方式进行同步位置 [DisallowMultipleComponent] [DefaultExecutionOrder(100000)] public class SyncTranform : NetworkBehaviour { //位置信息变化阈值,当单次变化大于该值,就会进行同步 public const float PositionThresholdDefault = 0.001f; public const float RotAngleThresholdDefault = 0.01f; public const float ScaleThresholdDefault = 0.01f; //客户端变化请求 public delegate (Vector3 pos, Quaternion rotOut, Vector3 scale) OnClientRequestChangeDelegate(Vector3 pos, Quaternion rot, Vector3 scale); //服务端收到客户端位置同步信息,回调 /// /// The handler that gets invoked when server receives a change from a client. /// This handler would be useful for server to modify pos/rot/scale before applying client's request. /// public OnClientRequestChangeDelegate OnClientRequestChange; //位置信息同步 internal struct NetworkTransformState : INetworkSerializable { private const int k_InLocalSpaceBit = 0; private const int k_PositionXBit = 1; private const int k_PositionYBit = 2; private const int k_PositionZBit = 3; private const int k_RotAngleXBit = 4; private const int k_RotAngleYBit = 5; private const int k_RotAngleZBit = 6; private const int k_ScaleXBit = 7; private const int k_ScaleYBit = 8; private const int k_ScaleZBit = 9; private const int k_TeleportingBit = 10; //是否局部空间的位置 private ushort m_Bitset; internal bool InLocalSpace { get => (m_Bitset & (1 << k_InLocalSpaceBit)) != 0; set { if(value) { m_Bitset = (ushort)(m_Bitset | (1 << k_InLocalSpaceBit)); } else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_InLocalSpaceBit)); } } } //处理位置 internal bool HasPositionX { get => (m_Bitset & (1 << k_PositionXBit)) != 0; set { if(value) { m_Bitset = (ushort)(m_Bitset | (1 << k_PositionXBit)); } else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_PositionXBit)); } } } internal bool HasPositionY { get => (m_Bitset & (1 << k_PositionYBit)) != 0; set { if(value) { m_Bitset = (ushort)(m_Bitset | (1 << k_PositionYBit)); } else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_PositionYBit)); } } } internal bool HasPositionZ { get => (m_Bitset & (1 << k_PositionZBit)) != 0; set { if(value) { m_Bitset = (ushort)(m_Bitset | (1 << k_PositionZBit)); } else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_PositionZBit)); } } } internal bool HasPositionChange { get { return HasPositionX | HasPositionY | HasPositionZ; } } //处理旋转 internal bool HasRotAngleX { get => (m_Bitset & (1 << k_RotAngleXBit)) != 0; set { if(value) { m_Bitset = (ushort)(m_Bitset | (1 << k_RotAngleXBit)); } else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_RotAngleXBit)); } } } internal bool HasRotAngleY { get => (m_Bitset & (1 << k_RotAngleYBit)) != 0; set { if(value) { m_Bitset = (ushort)(m_Bitset | (1 << k_RotAngleYBit)); } else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_RotAngleYBit)); } } } internal bool HasRotAngleZ { get => (m_Bitset & (1 << k_RotAngleZBit)) != 0; set { if(value) { m_Bitset = (ushort)(m_Bitset | (1 << k_RotAngleZBit)); } else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_RotAngleZBit)); } } } internal bool HasRotAngleChange { get { return HasRotAngleX | HasRotAngleY | HasRotAngleZ; } } // 处理缩放 internal bool HasScaleX { get => (m_Bitset & (1 << k_ScaleXBit)) != 0; set { if(value) { m_Bitset = (ushort)(m_Bitset | (1 << k_ScaleXBit)); } else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_ScaleXBit)); } } } internal bool HasScaleY { get => (m_Bitset & (1 << k_ScaleYBit)) != 0; set { if(value) { m_Bitset = (ushort)(m_Bitset | (1 << k_ScaleYBit)); } else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_ScaleYBit)); } } } internal bool HasScaleZ { get => (m_Bitset & (1 << k_ScaleZBit)) != 0; set { if(value) { m_Bitset = (ushort)(m_Bitset | (1 << k_ScaleZBit)); } else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_ScaleZBit)); } } } internal bool HasScaleChange { get { return HasScaleX | HasScaleY | HasScaleZ; } } internal bool IsTeleportingNextFrame { get => (m_Bitset & (1 << k_TeleportingBit)) != 0; set { if(value) { m_Bitset = (ushort)(m_Bitset | (1 << k_TeleportingBit)); } else { m_Bitset = (ushort)(m_Bitset & ~(1 << k_TeleportingBit)); } } } internal float PositionX, PositionY, PositionZ; internal float RotAngleX, RotAngleY, RotAngleZ; internal float ScaleX, ScaleY, ScaleZ; internal double SentTime; //数据有更新 internal bool IsDirty; //用于结束外插值 internal int EndExtrapolationTick; //重置BitSet internal void ClearBitSetForNextTick() { //保存局部空间设置 m_Bitset &= (ushort)(m_Bitset & (1 << k_InLocalSpaceBit)); IsDirty = false; } public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { serializer.SerializeValue(ref SentTime); serializer.SerializeValue(ref m_Bitset); if(HasPositionX) { serializer.SerializeValue(ref PositionX); } if(HasPositionY) { serializer.SerializeValue(ref PositionY); } if(HasPositionZ) { serializer.SerializeValue(ref PositionZ); } if(HasRotAngleX) { serializer.SerializeValue(ref RotAngleX); } if(HasRotAngleY) { serializer.SerializeValue(ref RotAngleY); } if(HasRotAngleZ) { serializer.SerializeValue(ref RotAngleZ); } if(HasScaleX) { serializer.SerializeValue(ref ScaleX); } if(HasScaleY) { serializer.SerializeValue(ref ScaleY); } if(HasScaleZ) { serializer.SerializeValue(ref ScaleZ); } //收到数据 if(serializer.IsReader) { //详见TryCommitTransformToServer方法 if(HasPositionChange || HasRotAngleChange || HasScaleChange) { IsDirty = true; } else { IsDirty = false; } } } } //是否同步Position public bool SyncPositionX = true; public bool SyncPositionY = true; public bool SyncPositionZ = true; private bool SynchronizePosition { get { return SyncPositionX || SyncPositionY || SyncPositionZ; } } //是否同步Rotaion public bool SyncRotAngleX = true; public bool SyncRotAngleY = true; public bool SyncRotAngleZ = true; private bool SynchronizeRotation { get { return SyncRotAngleX || SyncRotAngleY || SyncRotAngleZ; } } //是否同步Scale public bool SyncScaleX = true; public bool SyncScaleY = true; public bool SyncScaleZ = true; private bool SynchronizeScale { get { return SyncScaleX || SyncScaleY || SyncScaleZ; } } //各个阈值 public float PositionThreshold = PositionThresholdDefault; [Range(0.001f, 360.0f)] public float RotAngleThreshold = RotAngleThresholdDefault; public float ScaleThreshold = ScaleThresholdDefault; [Tooltip("Sets whether this transform should sync in local space or in world space")] public bool InLocalSpace = false; //是否开启插值 public bool Interpolate = true; public bool UseRelativeTransform; //是否应用坐标变化 /// /// Used to determine who can write to this transform. Server only for this transform. /// Changing this value alone in a child implementation will not allow you to create a NetworkTransform which can be written to by clients. See the ClientNetworkTransform Sample /// in the package samples for how to implement a NetworkTransform with client write support. /// If using different values, please use RPCs to write to the server. Netcode doesn't support client side network variable writing /// public bool CanCommitToTransform { get; protected set; } /// /// Internally used by to keep track of whether this derived class instance /// was instantiated on the server side or not. /// protected bool m_CachedIsServer; /// /// Internally used by to keep track of the instance assigned to this /// this derived class instance. /// protected NetworkManager m_CachedNetworkManager; /// /// We have two internal NetworkVariables. /// One for server authoritative and one for "client/owner" authoritative. /// private readonly NetworkVariable m_ReplicatedNetworkStateServer = new NetworkVariable(new NetworkTransformState(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); private readonly NetworkVariable m_ReplicatedNetworkStateOwner = new NetworkVariable(new NetworkTransformState(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); internal NetworkVariable ReplicatedNetworkState { get { if(!IsServerAuthoritative()) { return m_ReplicatedNetworkStateOwner; } return m_ReplicatedNetworkStateServer; } } // Used by both authoritative and non-authoritative instances. // This represents the most recent local authoritative state. private NetworkTransformState m_LocalAuthoritativeNetworkState; private ClientRpcParams m_ClientRpcParams = new ClientRpcParams() { Send = new ClientRpcSendParams() }; private List m_ClientIds = new List() { 0 }; private BufferedLinearInterpolator m_PositionXInterpolator; private BufferedLinearInterpolator m_PositionYInterpolator; private BufferedLinearInterpolator m_PositionZInterpolator; private BufferedLinearInterpolator m_RotationInterpolator; // rotation is a single Quaternion since each Euler axis will affect the quaternion's final value private BufferedLinearInterpolator m_ScaleXInterpolator; private BufferedLinearInterpolator m_ScaleYInterpolator; private BufferedLinearInterpolator m_ScaleZInterpolator; private readonly List> m_AllFloatInterpolators = new List>(6); private NetworkTransformState m_LastSentState; internal NetworkTransformState GetLastSentState() { return m_LastSentState; } /// /// Calculated when spawned, this is used to offset a newly received non-authority side state by 1 tick duration /// in order to end the extrapolation for that state's values. /// /// /// Example: /// NetworkState-A is received, processed, and measurements added /// NetworkState-A is duplicated (NetworkState-A-Post) and its sent time is offset by the tick frequency /// One tick later, NetworkState-A-Post is applied to end that delta's extrapolation. /// to see how NetworkState-A-Post doesn't get excluded/missed /// private double m_TickFrequency; /// /// This will try to send/commit the current transform delta states (if any) /// /// /// Only client owners or the server should invoke this method /// /// the transform to be committed /// time it was marked dirty protected void TryCommitTransformToServer(Transform transformToCommit, double dirtyTime) { // Only client owners or the server should invoke this method if(!IsOwner && !m_CachedIsServer) { NetworkLog.LogError($"Non-owner instance, {name}, is trying to commit a transform!"); return; } // If we are authority, update the authoritative state if(CanCommitToTransform) { UpdateAuthoritativeState(transform); } else // Non-Authority { // We are an owner requesting to update our state if(!m_CachedIsServer) { SetStateServerRpc(transformToCommit.position, transformToCommit.rotation, transformToCommit.localScale, false); } else // Server is always authoritative (including owner authoritative) { SetStateClientRpc(transformToCommit.position, transformToCommit.rotation, transformToCommit.localScale, false); } } } /// /// Authoritative side only /// If there are any transform delta states, this method will synchronize the /// state with all non-authority instances. /// private void TryCommitTransform(Transform transformToCommit, double dirtyTime) { if(!CanCommitToTransform && !IsOwner) { NetworkLog.LogError($"[{name}] is trying to commit the transform without authority!"); return; } // If the transform has deltas (returns dirty) then... if(ApplyTransformToNetworkState(ref m_LocalAuthoritativeNetworkState, dirtyTime, transformToCommit)) { // ...commit the state ReplicatedNetworkState.Value = m_LocalAuthoritativeNetworkState; } } private void ResetInterpolatedStateToCurrentAuthoritativeState() { var serverTime = NetworkManager.ServerTime.Time; // TODO: Look into a better way to communicate the entire state for late joining clients. // Since the replicated network state will just be the most recent deltas and not the entire state. //m_PositionXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionX, serverTime); //m_PositionYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionY, serverTime); //m_PositionZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.PositionZ, serverTime); //m_RotationInterpolator.ResetTo(Quaternion.Euler(m_LocalAuthoritativeNetworkState.RotAngleX, m_LocalAuthoritativeNetworkState.RotAngleY, m_LocalAuthoritativeNetworkState.RotAngleZ), serverTime); //m_ScaleXInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleX, serverTime); //m_ScaleYInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleY, serverTime); //m_ScaleZInterpolator.ResetTo(m_LocalAuthoritativeNetworkState.ScaleZ, serverTime); // NOTE ABOUT THIS CHANGE: // !!! This will exclude any scale changes because we currently do not spawn network objects with scale !!! // Regarding Scale: It will be the same scale as the default scale for the object being spawned. var position = InLocalSpace ? transform.localPosition : transform.position; m_PositionXInterpolator.ResetTo(position.x, serverTime); m_PositionYInterpolator.ResetTo(position.y, serverTime); m_PositionZInterpolator.ResetTo(position.z, serverTime); var rotation = InLocalSpace ? transform.localRotation : transform.rotation; m_RotationInterpolator.ResetTo(rotation, serverTime); // TODO: (Create Jira Ticket) Synchronize local scale during NetworkObject synchronization // (We will probably want to byte pack TransformData to offset the 3 float addition) m_ScaleXInterpolator.ResetTo(transform.localScale.x, serverTime); m_ScaleYInterpolator.ResetTo(transform.localScale.y, serverTime); m_ScaleZInterpolator.ResetTo(transform.localScale.z, serverTime); } /// /// Used for integration testing: /// Will apply the transform to the LocalAuthoritativeNetworkState and get detailed dirty information returned /// in the returned. /// /// transform to apply /// NetworkTransformState internal NetworkTransformState ApplyLocalNetworkState(Transform transform) { // Since we never commit these changes, we need to simulate that any changes were committed previously and the bitset // value would already be reset prior to having the state applied m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick(); // Now check the transform for any threshold value changes ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, m_CachedNetworkManager.LocalTime.Time, transform); // Return the entire state to be used by the integration test return m_LocalAuthoritativeNetworkState; } /// /// Used for integration testing /// internal bool ApplyTransformToNetworkState(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse) { return ApplyTransformToNetworkStateWithInfo(ref networkState, dirtyTime, transformToUse); } /// /// Applies the transform to the specified. /// private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState networkState, double dirtyTime, Transform transformToUse) { var isDirty = false; var isPositionDirty = false; var isRotationDirty = false; var isScaleDirty = false; var position = InLocalSpace ? transformToUse.localPosition : transformToUse.position; var rotAngles = InLocalSpace ? transformToUse.localEulerAngles : transformToUse.eulerAngles; var scale = transformToUse.localScale; if(InLocalSpace != networkState.InLocalSpace) { networkState.InLocalSpace = InLocalSpace; isDirty = true; } if(SyncPositionX && Mathf.Abs(networkState.PositionX - position.x) >= PositionThreshold || networkState.IsTeleportingNextFrame) { networkState.PositionX = position.x; networkState.HasPositionX = true; isPositionDirty = true; } if(SyncPositionY && Mathf.Abs(networkState.PositionY - position.y) >= PositionThreshold || networkState.IsTeleportingNextFrame) { networkState.PositionY = position.y; networkState.HasPositionY = true; isPositionDirty = true; } if(SyncPositionZ && Mathf.Abs(networkState.PositionZ - position.z) >= PositionThreshold || networkState.IsTeleportingNextFrame) { networkState.PositionZ = position.z; networkState.HasPositionZ = true; isPositionDirty = true; } if(SyncRotAngleX && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleX, rotAngles.x)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame) { networkState.RotAngleX = rotAngles.x; networkState.HasRotAngleX = true; isRotationDirty = true; } if(SyncRotAngleY && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleY, rotAngles.y)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame) { networkState.RotAngleY = rotAngles.y; networkState.HasRotAngleY = true; isRotationDirty = true; } if(SyncRotAngleZ && Mathf.Abs(Mathf.DeltaAngle(networkState.RotAngleZ, rotAngles.z)) >= RotAngleThreshold || networkState.IsTeleportingNextFrame) { networkState.RotAngleZ = rotAngles.z; networkState.HasRotAngleZ = true; isRotationDirty = true; } if(SyncScaleX && Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold || networkState.IsTeleportingNextFrame) { networkState.ScaleX = scale.x; networkState.HasScaleX = true; isScaleDirty = true; } if(SyncScaleY && Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold || networkState.IsTeleportingNextFrame) { networkState.ScaleY = scale.y; networkState.HasScaleY = true; isScaleDirty = true; } if(SyncScaleZ && Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold || networkState.IsTeleportingNextFrame) { networkState.ScaleZ = scale.z; networkState.HasScaleZ = true; isScaleDirty = true; } isDirty |= isPositionDirty || isRotationDirty || isScaleDirty; if(isDirty) { networkState.SentTime = dirtyTime; } /// We need to set this in order to know when we can reset our local authority state /// If our state is already dirty or we just found deltas (i.e. isDirty == true) networkState.IsDirty |= isDirty; return isDirty; } //Update,将authoritative state修改位置 private void ApplyAuthoritativeState() { // var networkState = ReplicatedNetworkState.Value; var adjustedPosition = networkState.InLocalSpace ? transform.localPosition : transform.position; var adjustedRotAngles = networkState.InLocalSpace ? transform.localEulerAngles : transform.eulerAngles; var adjustedScale = transform.localScale; InLocalSpace = networkState.InLocalSpace; //插值 // NOTE ABOUT INTERPOLATING AND THE CODE BELOW: // We always apply the interpolated state for any axis we are synchronizing even when the state has no deltas // to assure we fully interpolate to our target even after we stop extrapolating 1 tick later. var useInterpolatedValue = !networkState.IsTeleportingNextFrame && Interpolate; if(useInterpolatedValue) { if(SyncPositionX) { adjustedPosition.x = m_PositionXInterpolator.GetInterpolatedValue(); } if(SyncPositionY) { adjustedPosition.y = m_PositionYInterpolator.GetInterpolatedValue(); } if(SyncPositionZ) { adjustedPosition.z = m_PositionZInterpolator.GetInterpolatedValue(); } if(SyncScaleX) { adjustedScale.x = m_ScaleXInterpolator.GetInterpolatedValue(); } if(SyncScaleY) { adjustedScale.y = m_ScaleYInterpolator.GetInterpolatedValue(); } if(SyncScaleZ) { adjustedScale.z = m_ScaleZInterpolator.GetInterpolatedValue(); } if(SynchronizeRotation) { var interpolatedEulerAngles = m_RotationInterpolator.GetInterpolatedValue().eulerAngles; if(SyncRotAngleX) { adjustedRotAngles.x = interpolatedEulerAngles.x; } if(SyncRotAngleY) { adjustedRotAngles.y = interpolatedEulerAngles.y; } if(SyncRotAngleZ) { adjustedRotAngles.z = interpolatedEulerAngles.z; } } } else { if(networkState.HasPositionX) { adjustedPosition.x = networkState.PositionX; } if(networkState.HasPositionY) { adjustedPosition.y = networkState.PositionY; } if(networkState.HasPositionZ) { adjustedPosition.z = networkState.PositionZ; } if(networkState.HasScaleX) { adjustedScale.x = networkState.ScaleX; } if(networkState.HasScaleY) { adjustedScale.y = networkState.ScaleY; } if(networkState.HasScaleZ) { adjustedScale.z = networkState.ScaleZ; } if(networkState.HasRotAngleX) { adjustedRotAngles.x = networkState.RotAngleX; } if(networkState.HasRotAngleY) { adjustedRotAngles.y = networkState.RotAngleY; } if(networkState.HasRotAngleZ) { adjustedRotAngles.z = networkState.RotAngleZ; } } // NOTE: The below conditional checks for applying axial values are required in order to // prevent the non-authoritative side from making adjustments when interpolation is off. // TODO: Determine if we want to enforce, frame by frame, the non-authoritative transform values. // We would want save the position, rotation, and scale (each individually) after applying each // authoritative transform state received. Otherwise, the non-authoritative side could make // changes to an axial value (if interpolation is turned off) until authority sends an update for // that same axial value. When interpolation is on, the state's values being synchronized are // always applied each frame. //位置有变化。或者正在插值和同步位置 if(networkState.HasPositionChange || (useInterpolatedValue && SynchronizePosition)) { if(InLocalSpace) { transform.localPosition = adjustedPosition; } else { if(UseRelativeTransform) { transform.position = adjustedPosition; } } } // Apply the new rotation if it has changed or we are interpolating and synchronizing rotation if(networkState.HasRotAngleChange || (useInterpolatedValue && SynchronizeRotation)) { if(InLocalSpace) { transform.localRotation = Quaternion.Euler(adjustedRotAngles); } else { if(UseRelativeTransform) { transform.rotation = Quaternion.Euler(adjustedRotAngles); } } } // Apply the new scale if it has changed or we are interpolating and synchronizing scale if(networkState.HasScaleChange || (useInterpolatedValue && SynchronizeScale)) { transform.localScale = adjustedScale; } } //修改位置 /// /// Only non-authoritative instances should invoke this /// private void AddInterpolatedState(NetworkTransformState newState) { var sentTime = newState.SentTime; var currentPosition = newState.InLocalSpace ? transform.localPosition : transform.position; var currentRotation = newState.InLocalSpace ? transform.localRotation : transform.rotation; var currentEulerAngles = currentRotation.eulerAngles; // When there is a change in interpolation or if teleporting, we reset if((newState.InLocalSpace != InLocalSpace) || newState.IsTeleportingNextFrame) { InLocalSpace = newState.InLocalSpace; var currentScale = transform.localScale; // we should clear our float interpolators foreach(var interpolator in m_AllFloatInterpolators) { interpolator.Clear(); } // we should clear our quaternion interpolator m_RotationInterpolator.Clear(); // Adjust based on which axis changed if(newState.HasPositionX) { m_PositionXInterpolator.ResetTo(newState.PositionX, sentTime); currentPosition.x = newState.PositionX; } if(newState.HasPositionY) { m_PositionYInterpolator.ResetTo(newState.PositionY, sentTime); currentPosition.y = newState.PositionY; } if(newState.HasPositionZ) { m_PositionZInterpolator.ResetTo(newState.PositionZ, sentTime); currentPosition.z = newState.PositionZ; } // Apply the position if(newState.InLocalSpace) { transform.localPosition = currentPosition; } else { transform.position = currentPosition; } // Adjust based on which axis changed if(newState.HasScaleX) { m_ScaleXInterpolator.ResetTo(newState.ScaleX, sentTime); currentScale.x = newState.ScaleX; } if(newState.HasScaleY) { m_ScaleYInterpolator.ResetTo(newState.ScaleY, sentTime); currentScale.y = newState.ScaleY; } if(newState.HasScaleZ) { m_ScaleZInterpolator.ResetTo(newState.ScaleZ, sentTime); currentScale.z = newState.ScaleZ; } // Apply the adjusted scale transform.localScale = currentScale; // Adjust based on which axis changed if(newState.HasRotAngleX) { currentEulerAngles.x = newState.RotAngleX; } if(newState.HasRotAngleY) { currentEulerAngles.y = newState.RotAngleY; } if(newState.HasRotAngleZ) { currentEulerAngles.z = newState.RotAngleZ; } // Apply the rotation currentRotation.eulerAngles = currentEulerAngles; transform.rotation = currentRotation; // Reset the rotation interpolator m_RotationInterpolator.ResetTo(currentRotation, sentTime); return; } // Apply axial changes from the new state if(newState.HasPositionX) { m_PositionXInterpolator.AddMeasurement(newState.PositionX, sentTime); } if(newState.HasPositionY) { m_PositionYInterpolator.AddMeasurement(newState.PositionY, sentTime); } if(newState.HasPositionZ) { m_PositionZInterpolator.AddMeasurement(newState.PositionZ, sentTime); } if(newState.HasScaleX) { m_ScaleXInterpolator.AddMeasurement(newState.ScaleX, sentTime); } if(newState.HasScaleY) { m_ScaleYInterpolator.AddMeasurement(newState.ScaleY, sentTime); } if(newState.HasScaleZ) { m_ScaleZInterpolator.AddMeasurement(newState.ScaleZ, sentTime); } // With rotation, we check if there are any changes first and // if so then apply the changes to the current Euler rotation // values. if(newState.HasRotAngleChange) { if(newState.HasRotAngleX) { currentEulerAngles.x = newState.RotAngleX; } if(newState.HasRotAngleY) { currentEulerAngles.y = newState.RotAngleY; } if(newState.HasRotAngleZ) { currentEulerAngles.z = newState.RotAngleZ; } currentRotation.eulerAngles = currentEulerAngles; m_RotationInterpolator.AddMeasurement(currentRotation, sentTime); } } /// /// Only non-authoritative instances should invoke this method /// private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransformState newState) { if(!NetworkObject.IsSpawned) { return; } if(CanCommitToTransform) { // we're the authority, we ignore incoming changes return; } if(Interpolate) { // Add measurements for the new state's deltas AddInterpolatedState(newState); } } /// /// Will set the maximum interpolation boundary for the interpolators of this instance. /// This value roughly translates to the maximum value of 't' in and /// for all transform elements being monitored by /// (i.e. Position, Rotation, and Scale) /// /// Maximum time boundary that can be used in a frame when interpolating between two values public void SetMaxInterpolationBound(float maxInterpolationBound) { //m_PositionXInterpolator.MaxInterpolationBound = maxInterpolationBound; //m_PositionYInterpolator.MaxInterpolationBound = maxInterpolationBound; //m_PositionZInterpolator.MaxInterpolationBound = maxInterpolationBound; //m_RotationInterpolator.MaxInterpolationBound = maxInterpolationBound; //m_ScaleXInterpolator.MaxInterpolationBound = maxInterpolationBound; //m_ScaleYInterpolator.MaxInterpolationBound = maxInterpolationBound; //m_ScaleZInterpolator.MaxInterpolationBound = maxInterpolationBound; } /// /// Create interpolators when first instantiated to avoid memory allocations if the /// associated NetworkObject persists (i.e. despawned but not destroyed or pools) /// private void Awake() { // Rotation is a single Quaternion since each Euler axis will affect the quaternion's final value m_RotationInterpolator = new BufferedLinearInterpolatorQuaternion(); // All other interpolators are BufferedLinearInterpolatorFloats m_PositionXInterpolator = new BufferedLinearInterpolatorFloat(); m_PositionYInterpolator = new BufferedLinearInterpolatorFloat(); m_PositionZInterpolator = new BufferedLinearInterpolatorFloat(); m_ScaleXInterpolator = new BufferedLinearInterpolatorFloat(); m_ScaleYInterpolator = new BufferedLinearInterpolatorFloat(); m_ScaleZInterpolator = new BufferedLinearInterpolatorFloat(); // Used to quickly iteration over the BufferedLinearInterpolatorFloat // instances if(m_AllFloatInterpolators.Count == 0) { m_AllFloatInterpolators.Add(m_PositionXInterpolator); m_AllFloatInterpolators.Add(m_PositionYInterpolator); m_AllFloatInterpolators.Add(m_PositionZInterpolator); m_AllFloatInterpolators.Add(m_ScaleXInterpolator); m_AllFloatInterpolators.Add(m_ScaleYInterpolator); m_AllFloatInterpolators.Add(m_ScaleZInterpolator); } } /// public override void OnNetworkSpawn() { m_CachedIsServer = IsServer; m_CachedNetworkManager = NetworkManager; m_TickFrequency = 1.0 / NetworkManager.NetworkConfig.TickRate; Initialize(); // This assures the initial spawning of the object synchronizes all connected clients // with the current transform values. This should not be placed within Initialize since // that can be invoked when ownership changes. if(CanCommitToTransform) { var currentPosition = InLocalSpace ? transform.localPosition : transform.position; var currentRotation = InLocalSpace ? transform.localRotation : transform.rotation; // Teleport to current position SetStateInternal(currentPosition, currentRotation, transform.localScale, true); // Force the state update to be sent TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time); } } /// public override void OnNetworkDespawn() { ReplicatedNetworkState.OnValueChanged -= OnNetworkStateChanged; } /// public override void OnDestroy() { base.OnDestroy(); m_ReplicatedNetworkStateServer.Dispose(); m_ReplicatedNetworkStateOwner.Dispose(); } /// public override void OnGainedOwnership() { Initialize(); } /// public override void OnLostOwnership() { Initialize(); } /// /// Initializes NetworkTransform when spawned and ownership changes. /// private void Initialize() { if(!IsSpawned) { return; } CanCommitToTransform = IsServerAuthoritative() ? IsServer : IsOwner; var replicatedState = ReplicatedNetworkState; m_LocalAuthoritativeNetworkState = replicatedState.Value; if(CanCommitToTransform) { replicatedState.OnValueChanged -= OnNetworkStateChanged; } else { replicatedState.OnValueChanged += OnNetworkStateChanged; // In case we are late joining ResetInterpolatedStateToCurrentAuthoritativeState(); } } /// /// Directly sets a state on the authoritative transform. /// Owner clients can directly set the state on a server authoritative transform /// This will override any changes made previously to the transform /// This isn't resistant to network jitter. Server side changes due to this method won't be interpolated. /// The parameters are broken up into pos / rot / scale on purpose so that the caller can perturb /// just the desired one(s) /// /// new position to move to. Can be null /// new rotation to rotate to. Can be null /// new scale to scale to. Can be null /// Should other clients interpolate this change or not. True by default /// new scale to scale to. Can be null /// public void SetState(Vector3? posIn = null, Quaternion? rotIn = null, Vector3? scaleIn = null, bool shouldGhostsInterpolate = true) { if(!IsSpawned) { return; } // Only the server or owner can invoke this method if(!IsOwner && !m_CachedIsServer) { throw new Exception("Non-owner client instance cannot set the state of the NetworkTransform!"); } Vector3 pos = posIn == null ? InLocalSpace ? transform.localPosition : transform.position : posIn.Value; Quaternion rot = rotIn == null ? InLocalSpace ? transform.localRotation : transform.rotation : rotIn.Value; Vector3 scale = scaleIn == null ? transform.localScale : scaleIn.Value; if(!CanCommitToTransform) { // Preserving the ability for owner authoritative mode to accept state changes from server if(m_CachedIsServer) { m_ClientIds[0] = OwnerClientId; m_ClientRpcParams.Send.TargetClientIds = m_ClientIds; SetStateClientRpc(pos, rot, scale, !shouldGhostsInterpolate, m_ClientRpcParams); } else // Preserving the ability for server authoritative mode to accept state changes from owner { SetStateServerRpc(pos, rot, scale, !shouldGhostsInterpolate); } return; } SetStateInternal(pos, rot, scale, !shouldGhostsInterpolate); } //修改位置 /// /// Authoritative only method /// Sets the internal state (teleporting or just set state) of the authoritative /// transform directly. /// private void SetStateInternal(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport) { if(InLocalSpace) { transform.localPosition = pos; transform.localRotation = rot; } else { transform.position = pos; transform.rotation = rot; } transform.localScale = scale; m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time); } //修改位置 /// /// Invoked by , allows a non-owner server to update the transform state /// /// /// Continued support for client-driven server authority model /// [ClientRpc] private void SetStateClientRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport, ClientRpcParams clientRpcParams = default) { // Server dictated state is always applied transform.position = pos; transform.rotation = rot; transform.localScale = scale; m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time); } //修改位置 /// /// Invoked by , allows an owner-client update the transform state /// /// /// Continued support for client-driven server authority model /// [ServerRpc] private void SetStateServerRpc(Vector3 pos, Quaternion rot, Vector3 scale, bool shouldTeleport) { // server has received this RPC request to move change transform. give the server a chance to modify or even reject the move if(OnClientRequestChange != null) { (pos, rot, scale) = OnClientRequestChange(pos, rot, scale); } transform.position = pos; transform.rotation = rot; transform.localScale = scale; m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = shouldTeleport; TryCommitTransform(transform, m_CachedNetworkManager.LocalTime.Time); } /// /// Will update the authoritative transform state if any deltas are detected. /// This will also reset the m_LocalAuthoritativeNetworkState if it is still dirty /// but the replicated network state is not. /// /// transform to be updated private void UpdateAuthoritativeState(Transform transformSource) { // If our replicated state is not dirty and our local authority state is dirty, clear it. if(!ReplicatedNetworkState.IsDirty() && m_LocalAuthoritativeNetworkState.IsDirty) { m_LastSentState = m_LocalAuthoritativeNetworkState; // Now clear our bitset and prepare for next network tick state update m_LocalAuthoritativeNetworkState.ClearBitSetForNextTick(); } TryCommitTransform(transformSource, m_CachedNetworkManager.LocalTime.Time); } /// /// /// If you override this method, be sure that: /// - Non-owners always invoke this base class method when using interpolation. /// - Authority can opt to use in place of invoking this base class method. /// - Non-authority owners can use but should still invoke the this base class method when using interpolation. /// protected virtual void Update() { if(!IsSpawned) { return; } // If we are authority, update the authoritative state if(CanCommitToTransform) { UpdateAuthoritativeState(transform); } else // Non-Authority { if(Interpolate) { var serverTime = NetworkManager.ServerTime; var cachedDeltaTime = Time.deltaTime; var cachedServerTime = serverTime.Time; var cachedRenderTime = serverTime.TimeTicksAgo(1).Time; foreach(var interpolator in m_AllFloatInterpolators) { interpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); } m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); } ApplyAuthoritativeState(); } } /// /// Teleport the transform to the given values without interpolating /// /// new position to move to. /// new rotation to rotate to. /// new scale to scale to. /// public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newScale) { if(!CanCommitToTransform) { throw new Exception("Teleporting on non-authoritative side is not allowed!"); } // Teleporting now is as simple as setting the internal state and passing the teleport flag SetStateInternal(newPosition, newRotation, newScale, true); } /// /// Override this method and return false to switch to owner authoritative mode /// /// ( or ) where when false it runs as owner-client authoritative protected virtual bool OnIsServerAuthoritative() { return true; } /// /// Used by to determines if this is server or owner authoritative. /// internal bool IsServerAuthoritative() { return OnIsServerAuthoritative(); } }