diff --git a/Mirror/Editor/Mirror.Editor.csproj b/Mirror/Editor/Mirror.Editor.csproj index b39e613ac8..eaef2fb2e5 100644 --- a/Mirror/Editor/Mirror.Editor.csproj +++ b/Mirror/Editor/Mirror.Editor.csproj @@ -60,8 +60,6 @@ - - diff --git a/Mirror/Editor/NetworkTransformChildEditor.cs b/Mirror/Editor/NetworkTransformChildEditor.cs deleted file mode 100644 index 134fc67431..0000000000 --- a/Mirror/Editor/NetworkTransformChildEditor.cs +++ /dev/null @@ -1,121 +0,0 @@ -using UnityEditor; -using UnityEngine; -using System.Collections; - -namespace Mirror -{ - [CustomEditor(typeof(NetworkTransformChild), true)] - [CanEditMultipleObjects] - public class NetworkTransformChildEditor : Editor - { - private static GUIContent[] axisOptions = {new GUIContent("None"), new GUIContent("X"), new GUIContent("Y (Top-Down 2D)"), new GUIContent("Z (Side-on 2D)"), new GUIContent("XY (FPS)"), new GUIContent("XZ"), new GUIContent("YZ"), new GUIContent("XYZ (full 3D)")}; - - bool m_Initialized; - NetworkTransformChild sync; - - SerializedProperty m_Target; - SerializedProperty m_MovementThreshold; - - SerializedProperty m_InterpolateRotation; - SerializedProperty m_InterpolateMovement; - SerializedProperty m_RotationSyncCompression; - - protected GUIContent m_TargetLabel; - protected GUIContent m_MovementThresholdLabel; - - protected GUIContent m_InterpolateRotationLabel; - protected GUIContent m_InterpolateMovementLabel; - protected GUIContent m_RotationSyncCompressionLabel; - protected GUIContent m_RotationAxisLabel; - - SerializedProperty m_SyncIntervalProperty; - public void Init() - { - if (m_Initialized) - return; - - m_Initialized = true; - sync = target as NetworkTransformChild; - - m_Target = serializedObject.FindProperty("m_Target"); - if (sync.GetComponent() == null) - { - Debug.LogError("NetworkTransformChild must be on the root object with the NetworkTransform, not on the child node"); - m_Target.objectReferenceValue = null; - } - - m_MovementThreshold = serializedObject.FindProperty("m_MovementThreshold"); - - m_InterpolateRotation = serializedObject.FindProperty("m_InterpolateRotation"); - m_InterpolateMovement = serializedObject.FindProperty("m_InterpolateMovement"); - m_RotationSyncCompression = serializedObject.FindProperty("m_RotationSyncCompression"); - - m_SyncIntervalProperty = serializedObject.FindProperty("syncInterval"); - m_TargetLabel = new GUIContent("Target", "The child transform to be synchronized."); - EditorGUI.indentLevel += 1; - m_MovementThresholdLabel = new GUIContent("Movement Threshold", "The distance that this object can move without sending a movement synchronization update."); - - m_InterpolateRotationLabel = new GUIContent("Interpolate Rotation Factor", "The larger this number is, the faster the object will interpolate to the target facing direction."); - m_InterpolateMovementLabel = new GUIContent("Interpolate Movement Factor", "The larger this number is, the faster the object will interpolate to the target position."); - m_RotationSyncCompressionLabel = new GUIContent("Compress Rotation", "How much to compress rotation sync updates.\n\nChoose None for no compression.\n\nChoose Low for a low amount of compression that preserves accuracy.\n\nChoose High for a high amount of compression that sacrifices accuracy."); - m_RotationAxisLabel = new GUIContent("Rotation Axis", "Which axis to use for rotation."); - - EditorGUI.indentLevel -= 1; - } - - protected void ShowControls() - { - if (m_Target == null) - { - m_Initialized = false; - } - Init(); - - serializedObject.Update(); - - // [0,2] should be enough. anything >2s is too laggy anyway. - m_SyncIntervalProperty.floatValue = EditorGUILayout.Slider( - new GUIContent("Network Sync Interval", - "Time in seconds until next change is synchronized to the client. '0' means send immediately if changed. '0.5' means only send changes every 500ms.\n(This is for state synchronization like SyncVars, SyncLists, OnSerialize. Not for Cmds, Rpcs, etc.)"), - m_SyncIntervalProperty.floatValue, 0, 2); - - if (EditorGUILayout.PropertyField(m_Target, m_TargetLabel)) - { - if (sync.GetComponent() == null) - { - Debug.LogError("NetworkTransformChild must be on the root object with the NetworkTransform, not on the child node"); - m_Target.objectReferenceValue = null; - } - } - - EditorGUILayout.PropertyField(m_MovementThreshold, m_MovementThresholdLabel); - if (m_MovementThreshold.floatValue < 0) - { - m_MovementThreshold.floatValue = 0; - EditorUtility.SetDirty(sync); - } - EditorGUILayout.PropertyField(m_InterpolateMovement, m_InterpolateMovementLabel); - - EditorGUILayout.PropertyField(m_InterpolateRotation, m_InterpolateRotationLabel); - - int newRotation = EditorGUILayout.Popup( - m_RotationAxisLabel, - (int)sync.syncRotationAxis, - axisOptions); - if ((NetworkTransform.AxisSyncMode)newRotation != sync.syncRotationAxis) - { - sync.syncRotationAxis = (NetworkTransform.AxisSyncMode)newRotation; - EditorUtility.SetDirty(sync); - } - - EditorGUILayout.PropertyField(m_RotationSyncCompression, m_RotationSyncCompressionLabel); - - serializedObject.ApplyModifiedProperties(); - } - - public override void OnInspectorGUI() - { - ShowControls(); - } - } -} diff --git a/Mirror/Editor/NetworkTransformEditor.cs b/Mirror/Editor/NetworkTransformEditor.cs deleted file mode 100644 index 8124473b50..0000000000 --- a/Mirror/Editor/NetworkTransformEditor.cs +++ /dev/null @@ -1,196 +0,0 @@ -using System; -using UnityEditor; -using UnityEngine; - -namespace Mirror -{ - [CustomEditor(typeof(NetworkTransform), true)] - [CanEditMultipleObjects] - public class NetworkTransformEditor : Editor - { - private static GUIContent[] axisOptions = {new GUIContent("None"), new GUIContent("X"), new GUIContent("Y (Top-Down 2D)"), new GUIContent("Z (Side-on 2D)"), new GUIContent("XY (FPS)"), new GUIContent("XZ"), new GUIContent("YZ"), new GUIContent("XYZ (full 3D)")}; - - bool m_Initialized; - NetworkTransform m_SyncTransform; - - SerializedProperty m_TransformSyncMode; - SerializedProperty m_MovementTheshold; - SerializedProperty m_VelocityThreshold; - SerializedProperty m_SnapThreshold; - - SerializedProperty m_InterpolateRotation; - SerializedProperty m_InterpolateMovement; - SerializedProperty m_RotationSyncCompression; - SerializedProperty m_SyncSpin; - - protected GUIContent m_MovementThesholdLabel; - protected GUIContent m_VelocityThresholdLabel; - protected GUIContent m_SnapThresholdLabel; - - protected GUIContent m_InterpolateRotationLabel; - protected GUIContent m_InterpolateMovementLabel; - protected GUIContent m_RotationSyncCompressionLabel; - protected GUIContent m_RotationAxisLabel; - protected GUIContent m_SyncSpinLabel; - - SerializedProperty m_SyncIntervalProperty; - - public void Init() - { - if (m_Initialized) - return; - - m_Initialized = true; - m_SyncTransform = target as NetworkTransform; - - if (m_SyncTransform.transformSyncMode == NetworkTransform.TransformSyncMode.SyncNone) - { - if (m_SyncTransform.GetComponent() != null) - { - m_SyncTransform.transformSyncMode = NetworkTransform.TransformSyncMode.SyncRigidbody3D; - m_SyncTransform.syncRotationAxis = NetworkTransform.AxisSyncMode.AxisXYZ; - EditorUtility.SetDirty(m_SyncTransform); - } - else if (m_SyncTransform.GetComponent() != null) - { - m_SyncTransform.transformSyncMode = NetworkTransform.TransformSyncMode.SyncRigidbody2D; - m_SyncTransform.syncRotationAxis = NetworkTransform.AxisSyncMode.AxisZ; - EditorUtility.SetDirty(m_SyncTransform); - } - else if (m_SyncTransform.GetComponent() != null) - { - m_SyncTransform.transformSyncMode = NetworkTransform.TransformSyncMode.SyncCharacterController; - m_SyncTransform.syncRotationAxis = NetworkTransform.AxisSyncMode.AxisXYZ; - EditorUtility.SetDirty(m_SyncTransform); - } - else - { - m_SyncTransform.transformSyncMode = NetworkTransform.TransformSyncMode.SyncTransform; - m_SyncTransform.syncRotationAxis = NetworkTransform.AxisSyncMode.AxisXYZ; - EditorUtility.SetDirty(m_SyncTransform); - } - } - m_TransformSyncMode = serializedObject.FindProperty("m_TransformSyncMode"); - m_MovementTheshold = serializedObject.FindProperty("m_MovementTheshold"); - m_VelocityThreshold = serializedObject.FindProperty("m_VelocityThreshold"); - m_SnapThreshold = serializedObject.FindProperty("m_SnapThreshold"); - - m_InterpolateRotation = serializedObject.FindProperty("m_InterpolateRotation"); - m_InterpolateMovement = serializedObject.FindProperty("m_InterpolateMovement"); - m_RotationSyncCompression = serializedObject.FindProperty("m_RotationSyncCompression"); - m_SyncSpin = serializedObject.FindProperty("m_SyncSpin"); - - m_SyncIntervalProperty = serializedObject.FindProperty("syncInterval"); - EditorGUI.indentLevel += 1; - m_MovementThesholdLabel = new GUIContent("Movement Threshold", "The distance that this object can move without sending a movement synchronization update."); - m_VelocityThresholdLabel = new GUIContent("Velocity Threshold", "The minimum velocity difference that will be synchronized over the network."); - m_SnapThresholdLabel = new GUIContent("Snap Threshold", "If a movement update puts this object further from its current position that this value, it will snap to the updated position instead of moving smoothly."); - - m_InterpolateRotationLabel = new GUIContent("Interpolate Rotation Factor", "The larger this number is, the faster the object will interpolate to the target facing direction."); - m_InterpolateMovementLabel = new GUIContent("Interpolate Movement Factor", "The larger this number is, the faster the object will interpolate to the target position."); - m_RotationSyncCompressionLabel = new GUIContent("Compress Rotation", "How much to compress rotation sync updates.\n\nChoose None for no compression.\n\nChoose Low for a low amount of compression that preserves accuracy.\n\nChoose High for a high amount of compression that sacrifices accuracy."); - m_RotationAxisLabel = new GUIContent("Rotation Axis", "Which axis to use for rotation."); - m_SyncSpinLabel = new GUIContent("Sync Angular Velocity", "Enable to sync angular velocity."); - EditorGUI.indentLevel -= 1; - } - - protected void ShowControls() - { - if (m_TransformSyncMode == null) - { - m_Initialized = false; - } - Init(); - - serializedObject.Update(); - - // [0,2] should be enough. anything >2s is too laggy anyway. - m_SyncIntervalProperty.floatValue = EditorGUILayout.Slider( - new GUIContent("Network Sync Interval", - "Time in seconds until next change is synchronized to the client. '0' means send immediately if changed. '0.5' means only send changes every 500ms.\n(This is for state synchronization like SyncVars, SyncLists, OnSerialize. Not for Cmds, Rpcs, etc.)"), - m_SyncIntervalProperty.floatValue, 0, 2); - - EditorGUILayout.PropertyField(m_TransformSyncMode); - if (m_TransformSyncMode.enumValueIndex == (int)NetworkTransform.TransformSyncMode.SyncRigidbody3D) - { - Rigidbody r3D = m_SyncTransform.GetComponent(); - if (r3D == null) - { - Debug.LogError("Object has no Rigidbody component."); - m_TransformSyncMode.enumValueIndex = (int)NetworkTransform.TransformSyncMode.SyncTransform; - EditorUtility.SetDirty(m_SyncTransform); - } - } - if (m_TransformSyncMode.enumValueIndex == (int)NetworkTransform.TransformSyncMode.SyncRigidbody2D) - { - Rigidbody2D r2D = m_SyncTransform.GetComponent(); - if (r2D == null) - { - Debug.LogError("Object has no Rigidbody2D component."); - m_TransformSyncMode.enumValueIndex = (int)NetworkTransform.TransformSyncMode.SyncTransform; - EditorUtility.SetDirty(m_SyncTransform); - } - } - if (m_TransformSyncMode.enumValueIndex == (int)NetworkTransform.TransformSyncMode.SyncCharacterController) - { - var cc = m_SyncTransform.GetComponent(); - if (cc == null) - { - Debug.LogError("Object has no CharacterController component."); - m_TransformSyncMode.enumValueIndex = (int)NetworkTransform.TransformSyncMode.SyncTransform; - EditorUtility.SetDirty(m_SyncTransform); - } - } - - EditorGUILayout.LabelField("Movement:"); - EditorGUI.indentLevel += 1; - EditorGUILayout.PropertyField(m_MovementTheshold, m_MovementThesholdLabel); - - if (m_VelocityThreshold.floatValue < 0) - { - m_VelocityThreshold.floatValue = 0; - EditorUtility.SetDirty(m_SyncTransform); - } - - if ((m_TransformSyncMode.enumValueIndex == (int)NetworkTransform.TransformSyncMode.SyncRigidbody3D) || (m_TransformSyncMode.enumValueIndex == (int)NetworkTransform.TransformSyncMode.SyncRigidbody2D)) - { - EditorGUILayout.PropertyField(m_VelocityThreshold, m_VelocityThresholdLabel); - } - - if (m_MovementTheshold.floatValue < 0) - { - m_MovementTheshold.floatValue = 0; - EditorUtility.SetDirty(m_SyncTransform); - } - EditorGUILayout.PropertyField(m_SnapThreshold, m_SnapThresholdLabel); - EditorGUILayout.PropertyField(m_InterpolateMovement, m_InterpolateMovementLabel); - EditorGUI.indentLevel -= 1; - - EditorGUILayout.LabelField("Rotation:"); - EditorGUI.indentLevel += 1; - - int newRotation = EditorGUILayout.Popup( - m_RotationAxisLabel, - (int)m_SyncTransform.syncRotationAxis, - axisOptions); - if ((NetworkTransform.AxisSyncMode)newRotation != m_SyncTransform.syncRotationAxis) - { - m_SyncTransform.syncRotationAxis = (NetworkTransform.AxisSyncMode)newRotation; - EditorUtility.SetDirty(m_SyncTransform); - } - - EditorGUILayout.PropertyField(m_InterpolateRotation, m_InterpolateRotationLabel); - EditorGUILayout.PropertyField(m_RotationSyncCompression, m_RotationSyncCompressionLabel); - EditorGUILayout.PropertyField(m_SyncSpin, m_SyncSpinLabel); - - EditorGUI.indentLevel -= 1; - - serializedObject.ApplyModifiedProperties(); - } - - public override void OnInspectorGUI() - { - ShowControls(); - } - } -} diff --git a/Mirror/Runtime/Messages.cs b/Mirror/Runtime/Messages.cs index 8f72c8265b..6a8ce3fcc2 100644 --- a/Mirror/Runtime/Messages.cs +++ b/Mirror/Runtime/Messages.cs @@ -436,7 +436,7 @@ public override void Serialize(NetworkWriter writer) } } - // A client sends this message to the server + // A client sends this message to the server // to calculate RTT and synchronize time class NetworkPingMessage : DoubleMessage { @@ -469,41 +469,23 @@ public override void Serialize(NetworkWriter writer) } } - class LocalChildTransformMessage : MessageBase - { - public uint netId; - public uint childIndex; - public byte[] payload; - - public override void Deserialize(NetworkReader reader) - { - netId = reader.ReadPackedUInt32(); - childIndex = reader.ReadPackedUInt32(); - payload = reader.ReadBytesAndSize(); - } - - public override void Serialize(NetworkWriter writer) - { - writer.WritePackedUInt32(netId); - writer.WritePackedUInt32(childIndex); - writer.WriteBytesAndSize(payload); - } - } - - class LocalPlayerTransformMessage : MessageBase + class TransformMessage : MessageBase { public uint netId; + public int componentIndex; public byte[] payload; public override void Deserialize(NetworkReader reader) { netId = reader.ReadPackedUInt32(); + componentIndex = (int)reader.ReadPackedUInt32(); payload = reader.ReadBytesAndSize(); } public override void Serialize(NetworkWriter writer) { writer.WritePackedUInt32(netId); + writer.WritePackedUInt32((uint)componentIndex); writer.WriteBytesAndSize(payload); } } diff --git a/Mirror/Runtime/Mirror.Runtime.csproj b/Mirror/Runtime/Mirror.Runtime.csproj index 223182b15f..b74acc46d8 100644 --- a/Mirror/Runtime/Mirror.Runtime.csproj +++ b/Mirror/Runtime/Mirror.Runtime.csproj @@ -73,8 +73,6 @@ - - @@ -82,6 +80,9 @@ + + + diff --git a/Mirror/Runtime/NetworkServer.cs b/Mirror/Runtime/NetworkServer.cs index a024f62487..e3f71edc3f 100644 --- a/Mirror/Runtime/NetworkServer.cs +++ b/Mirror/Runtime/NetworkServer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -77,8 +77,7 @@ internal static void RegisterMessageHandlers() { RegisterHandler(MsgType.Ready, OnClientReadyMessage); RegisterHandler(MsgType.Command, OnCommandMessage); - RegisterHandler(MsgType.LocalPlayerTransform, NetworkTransform.HandleTransform); - RegisterHandler(MsgType.LocalChildTransform, NetworkTransformChild.HandleChildTransform); + RegisterHandler(MsgType.LocalPlayerTransform, NetworkTransformBase.OnClientToServerSync); RegisterHandler(MsgType.RemovePlayer, OnRemovePlayerMessage); RegisterHandler(MsgType.Animation, NetworkAnimator.OnAnimationServerMessage); RegisterHandler(MsgType.AnimationParameters, NetworkAnimator.OnAnimationParametersServerMessage); diff --git a/Mirror/Runtime/NetworkTransform.cs b/Mirror/Runtime/NetworkTransform.cs index 42cd93626c..8208c5453c 100644 --- a/Mirror/Runtime/NetworkTransform.cs +++ b/Mirror/Runtime/NetworkTransform.cs @@ -1,1456 +1,11 @@ -using System; using UnityEngine; namespace Mirror { [DisallowMultipleComponent] [AddComponentMenu("Network/NetworkTransform")] - public class NetworkTransform : NetworkBehaviour + public class NetworkTransform : NetworkTransformBase { - public enum TransformSyncMode - { - SyncNone = 0, - SyncTransform = 1, - SyncRigidbody2D = 2, - SyncRigidbody3D = 3, - SyncCharacterController = 4 - } - - public enum AxisSyncMode - { - None, - AxisX, - AxisY, - AxisZ, - AxisXY, - AxisXZ, - AxisYZ, - AxisXYZ - } - - public enum CompressionSyncMode - { - None, - Low, - High - } - - public delegate bool ClientMoveCallback3D(ref Vector3 position, ref Vector3 velocity, ref Quaternion rotation); - public delegate bool ClientMoveCallback2D(ref Vector2 position, ref Vector2 velocity, ref float rotation); - - [SerializeField] TransformSyncMode m_TransformSyncMode = TransformSyncMode.SyncNone; - [SerializeField] AxisSyncMode m_SyncRotationAxis = AxisSyncMode.AxisXYZ; - [SerializeField] CompressionSyncMode m_RotationSyncCompression = CompressionSyncMode.None; - [SerializeField] bool m_SyncSpin; - [SerializeField] float m_MovementTheshold = 0.001f; - [SerializeField] float m_VelocityThreshold = 0.0001f; - - [SerializeField] float m_SnapThreshold = 5.0f; - [SerializeField] float m_InterpolateRotation = 1.0f; - [SerializeField] float m_InterpolateMovement = 1.0f; - [SerializeField] ClientMoveCallback3D m_ClientMoveCallback3D; - [SerializeField] ClientMoveCallback2D m_ClientMoveCallback2D; - - Rigidbody m_RigidBody3D; - Rigidbody2D m_RigidBody2D; - CharacterController m_CharacterController; - bool m_Grounded = true; - - // movement smoothing - - Vector3 m_TargetSyncPosition; - Vector3 m_TargetSyncVelocity; - - Vector3 m_FixedPosDiff; - - Quaternion m_TargetSyncRotation3D; - Vector3 m_TargetSyncAngularVelocity3D; - - float m_TargetSyncRotation2D; - float m_TargetSyncAngularVelocity2D; - - float m_LastClientSyncTime; // last time client received a sync from server - float m_LastClientSendTime; // last time client send a sync to server - - Vector3 m_PrevPosition; - Quaternion m_PrevRotation; - float m_PrevRotation2D; - float m_PrevVelocity; - - const float k_LocalMovementThreshold = 0.00001f; - const float k_LocalRotationThreshold = 0.00001f; - const float k_LocalVelocityThreshold = 0.00001f; - const float k_MoveAheadRatio = 0.1f; - - // settings - - public TransformSyncMode transformSyncMode { get { return m_TransformSyncMode; } set { m_TransformSyncMode = value; } } - public AxisSyncMode syncRotationAxis { get { return m_SyncRotationAxis; } set { m_SyncRotationAxis = value; } } - public CompressionSyncMode rotationSyncCompression { get { return m_RotationSyncCompression; } set { m_RotationSyncCompression = value; } } - public bool syncSpin { get { return m_SyncSpin; } set { m_SyncSpin = value; } } - public float movementTheshold { get { return m_MovementTheshold; } set { m_MovementTheshold = value; } } - public float velocityThreshold { get { return m_VelocityThreshold; } set { m_VelocityThreshold = value; } } - public float snapThreshold { get { return m_SnapThreshold; } set { m_SnapThreshold = value; } } - public float interpolateRotation { get { return m_InterpolateRotation; } set { m_InterpolateRotation = value; } } - public float interpolateMovement { get { return m_InterpolateMovement; } set { m_InterpolateMovement = value; } } - public ClientMoveCallback3D clientMoveCallback3D { get { return m_ClientMoveCallback3D; } set { m_ClientMoveCallback3D = value; } } - public ClientMoveCallback2D clientMoveCallback2D { get { return m_ClientMoveCallback2D; } set { m_ClientMoveCallback2D = value; } } - - // runtime data - - public CharacterController characterContoller { get { return m_CharacterController; } } - public Rigidbody rigidbody3D { get { return m_RigidBody3D; } } -#if !PLATFORM_WINRT - new -#endif - public Rigidbody2D rigidbody2D { get { return m_RigidBody2D; } } - public float lastSyncTime { get { return m_LastClientSyncTime; } } - public Vector3 targetSyncPosition { get { return m_TargetSyncPosition; } } - public Vector3 targetSyncVelocity { get { return m_TargetSyncVelocity; } } - public Quaternion targetSyncRotation3D { get { return m_TargetSyncRotation3D; } } - public float targetSyncRotation2D { get { return m_TargetSyncRotation2D; } } - public bool grounded { get { return m_Grounded; } set { m_Grounded = value; } } - - void OnValidate() - { - if (m_TransformSyncMode < TransformSyncMode.SyncNone || m_TransformSyncMode > TransformSyncMode.SyncCharacterController) - { - m_TransformSyncMode = TransformSyncMode.SyncTransform; - } - - if (m_SyncRotationAxis < AxisSyncMode.None || m_SyncRotationAxis > AxisSyncMode.AxisXYZ) - { - m_SyncRotationAxis = AxisSyncMode.None; - } - - if (m_MovementTheshold < 0) - { - m_MovementTheshold = 0.00f; - } - - if (m_VelocityThreshold < 0) - { - m_VelocityThreshold = 0.00f; - } - - if (m_SnapThreshold < 0) - { - m_SnapThreshold = 0.01f; - } - - if (m_InterpolateRotation < 0) - { - m_InterpolateRotation = 0.01f; - } - - if (m_InterpolateMovement < 0) - { - m_InterpolateMovement = 0.01f; - } - } - - void Awake() - { - m_RigidBody3D = GetComponent(); - m_RigidBody2D = GetComponent(); - m_CharacterController = GetComponent(); - m_PrevPosition = transform.position; - m_PrevRotation = transform.rotation; - m_PrevVelocity = 0; - } - - public override void OnStartServer() - { - m_LastClientSyncTime = 0; - } - - public override bool OnSerialize(NetworkWriter writer, bool initialState) - { - if (initialState) - { - // always write initial state, no dirty bits - } - else if (syncVarDirtyBits == 0) - { - writer.WritePackedUInt32(0); - return false; - } - else - { - // dirty bits - writer.WritePackedUInt32(1); - } - - switch (transformSyncMode) - { - case TransformSyncMode.SyncNone: - return false; - case TransformSyncMode.SyncTransform: - SerializeModeTransform(writer); - break; - case TransformSyncMode.SyncRigidbody3D: - SerializeMode3D(writer); - break; - case TransformSyncMode.SyncRigidbody2D: - SerializeMode2D(writer); - break; - case TransformSyncMode.SyncCharacterController: - SerializeModeCharacterController(writer); - break; - } - return true; - } - - void SerializeModeTransform(NetworkWriter writer) - { - // position - writer.Write(transform.position); - - // no velocity - - // rotation - if (m_SyncRotationAxis != AxisSyncMode.None) - { - SerializeRotation3D(writer, transform.rotation, syncRotationAxis, rotationSyncCompression); - } - - // no spin - - m_PrevPosition = transform.position; - m_PrevRotation = transform.rotation; - m_PrevVelocity = 0; - } - - void VerifySerializeComponentExists() - { - switch (transformSyncMode) - { - case TransformSyncMode.SyncCharacterController: - m_CharacterController = m_CharacterController ?? GetComponent(); - if (!m_CharacterController) - { - throw new InvalidOperationException(string.Format("transformSyncMode set to {0} but no CharacterController component was found, did you call NetworkServer.Spawn on a prefab?", transformSyncMode)); - } - break; - - case TransformSyncMode.SyncRigidbody2D: - m_RigidBody2D = m_RigidBody2D ?? GetComponent(); - if (!m_RigidBody2D) - { - throw new InvalidOperationException(string.Format("transformSyncMode set to {0} but no Rigidbody2D component was found, did you call NetworkServer.Spawn on a prefab?", transformSyncMode)); - } - break; - - case TransformSyncMode.SyncRigidbody3D: - m_RigidBody3D = m_RigidBody3D ?? GetComponent(); - if (!m_RigidBody3D) - { - throw new InvalidOperationException(string.Format("transformSyncMode set to {0} but no Rigidbody component was found, did you call NetworkServer.Spawn on a prefab?", transformSyncMode)); - } - break; - } - } - - void SerializeMode3D(NetworkWriter writer) - { - VerifySerializeComponentExists(); - - if (isServer && m_LastClientSyncTime != 0) - { - // target position - writer.Write(m_TargetSyncPosition); - - // target velocity - SerializeVelocity3D(writer, m_TargetSyncVelocity, CompressionSyncMode.None); - - if (syncRotationAxis != AxisSyncMode.None) - { - // target rotation - SerializeRotation3D(writer, m_TargetSyncRotation3D, syncRotationAxis, rotationSyncCompression); - } - } - else - { - // current position - writer.Write(m_RigidBody3D.position); - - // current velocity - SerializeVelocity3D(writer, m_RigidBody3D.velocity, CompressionSyncMode.None); - - if (syncRotationAxis != AxisSyncMode.None) - { - // current rotation - SerializeRotation3D(writer, m_RigidBody3D.rotation, syncRotationAxis, rotationSyncCompression); - } - } - - // spin - if (m_SyncSpin) - { - SerializeSpin3D(writer, m_RigidBody3D.angularVelocity, syncRotationAxis, rotationSyncCompression); - } - - m_PrevPosition = m_RigidBody3D.position; - m_PrevRotation = transform.rotation; - m_PrevVelocity = m_RigidBody3D.velocity.sqrMagnitude; - } - - void SerializeModeCharacterController(NetworkWriter writer) - { - VerifySerializeComponentExists(); - - if (isServer && m_LastClientSyncTime != 0) - { - // target position - writer.Write(m_TargetSyncPosition); - - // no velocity - - if (syncRotationAxis != AxisSyncMode.None) - { - // target rotation - SerializeRotation3D(writer, m_TargetSyncRotation3D, syncRotationAxis, rotationSyncCompression); - } - } - else - { - // current position - writer.Write(transform.position); - - // no velocity - - if (syncRotationAxis != AxisSyncMode.None) - { - // current rotation - SerializeRotation3D(writer, transform.rotation, syncRotationAxis, rotationSyncCompression); - } - } - - // no spin - - m_PrevPosition = transform.position; - m_PrevRotation = transform.rotation; - m_PrevVelocity = 0; - } - - void SerializeMode2D(NetworkWriter writer) - { - VerifySerializeComponentExists(); - - if (isServer && m_LastClientSyncTime != 0) - { - // target position - writer.Write((Vector2)m_TargetSyncPosition); - - // target velocity - SerializeVelocity2D(writer, m_TargetSyncVelocity, CompressionSyncMode.None); - - // target rotation - if (syncRotationAxis != AxisSyncMode.None) - { - float orientation = m_TargetSyncRotation2D % 360; - if (orientation < 0) orientation += 360; - SerializeRotation2D(writer, orientation, rotationSyncCompression); - } - } - else - { - // current position - writer.Write(m_RigidBody2D.position); - - // current velocity - SerializeVelocity2D(writer, m_RigidBody2D.velocity, CompressionSyncMode.None); - - // current rotation - if (syncRotationAxis != AxisSyncMode.None) - { - float orientation = m_RigidBody2D.rotation % 360; - if (orientation < 0) orientation += 360; - SerializeRotation2D(writer, orientation, rotationSyncCompression); - } - } - - // spin - if (m_SyncSpin) - { - SerializeSpin2D(writer, m_RigidBody2D.angularVelocity, rotationSyncCompression); - } - - m_PrevPosition = m_RigidBody2D.position; - m_PrevRotation = transform.rotation; - m_PrevVelocity = m_RigidBody2D.velocity.sqrMagnitude; - } - - public override void OnDeserialize(NetworkReader reader, bool initialState) - { - if (isServer && NetworkServer.localClientActive) - return; - - if (!initialState) - { - if (reader.ReadPackedUInt32() == 0) - return; - } - - switch (transformSyncMode) - { - case TransformSyncMode.SyncNone: - return; - case TransformSyncMode.SyncTransform: - UnserializeModeTransform(reader, initialState); - break; - case TransformSyncMode.SyncRigidbody3D: - UnserializeMode3D(reader, initialState); - break; - case TransformSyncMode.SyncRigidbody2D: - UnserializeMode2D(reader, initialState); - break; - case TransformSyncMode.SyncCharacterController: - UnserializeModeCharacterController(reader, initialState); - break; - } - m_LastClientSyncTime = Time.time; - } - - void UnserializeModeTransform(NetworkReader reader, bool initialState) - { - if (hasAuthority) - { - // this component must read the data that the server wrote, even if it ignores it. - // otherwise the NetworkReader stream will still contain that data for the next component. - - // position - reader.ReadVector3(); - - if (syncRotationAxis != AxisSyncMode.None) - { - UnserializeRotation3D(reader, syncRotationAxis, rotationSyncCompression); - } - return; - } - - if (isServer && m_ClientMoveCallback3D != null) - { - var pos = reader.ReadVector3(); - var vel = Vector3.zero; - var rot = Quaternion.identity; - if (syncRotationAxis != AxisSyncMode.None) - { - rot = UnserializeRotation3D(reader, syncRotationAxis, rotationSyncCompression); - } - - if (m_ClientMoveCallback3D(ref pos, ref vel, ref rot)) - { - transform.position = pos; - if (syncRotationAxis != AxisSyncMode.None) - { - transform.rotation = rot; - } - } - } - else - { - // position - transform.position = reader.ReadVector3(); - - // no velocity - - // rotation - if (syncRotationAxis != AxisSyncMode.None) - { - transform.rotation = UnserializeRotation3D(reader, syncRotationAxis, rotationSyncCompression); - } - - // no spin - } - } - - void UnserializeMode3D(NetworkReader reader, bool initialState) - { - if (hasAuthority) - { - // this component must read the data that the server wrote, even if it ignores it. - // otherwise the NetworkReader stream will still contain that data for the next component. - - // position - reader.ReadVector3(); - - // velocity - reader.ReadVector3(); - - if (syncRotationAxis != AxisSyncMode.None) - { - UnserializeRotation3D(reader, syncRotationAxis, rotationSyncCompression); - } - if (syncSpin) - { - UnserializeSpin3D(reader, syncRotationAxis, rotationSyncCompression); - } - return; - } - - if (isServer && m_ClientMoveCallback3D != null) - { - var pos = reader.ReadVector3(); - var vel = reader.ReadVector3(); - Quaternion rot = Quaternion.identity; - if (syncRotationAxis != AxisSyncMode.None) - { - rot = UnserializeRotation3D(reader, syncRotationAxis, rotationSyncCompression); - } - - if (m_ClientMoveCallback3D(ref pos, ref vel, ref rot)) - { - m_TargetSyncPosition = pos; - m_TargetSyncVelocity = vel; - if (syncRotationAxis != AxisSyncMode.None) - { - m_TargetSyncRotation3D = rot; - } - } - else - { - // rejected by callback - return; - } - } - else - { - // position - m_TargetSyncPosition = reader.ReadVector3(); - - // velocity - m_TargetSyncVelocity = reader.ReadVector3(); - - // rotation - if (syncRotationAxis != AxisSyncMode.None) - { - m_TargetSyncRotation3D = UnserializeRotation3D(reader, syncRotationAxis, rotationSyncCompression); - } - } - // spin - if (syncSpin) - { - m_TargetSyncAngularVelocity3D = UnserializeSpin3D(reader, syncRotationAxis, rotationSyncCompression); - } - - if (m_RigidBody3D == null) - return; - - if (isServer && !isClient) - { - // dedicated server needs to apply immediately, there is no interpolation - m_RigidBody3D.MovePosition(m_TargetSyncPosition); - m_RigidBody3D.MoveRotation(m_TargetSyncRotation3D); - m_RigidBody3D.velocity = m_TargetSyncVelocity; - return; - } - - // handle zero send rate - if (syncInterval == 0) - { - m_RigidBody3D.MovePosition(m_TargetSyncPosition); - m_RigidBody3D.velocity = m_TargetSyncVelocity; - if (syncRotationAxis != AxisSyncMode.None) - { - m_RigidBody3D.MoveRotation(m_TargetSyncRotation3D); - } - if (syncSpin) - { - m_RigidBody3D.angularVelocity = m_TargetSyncAngularVelocity3D; - } - return; - } - - // handle position snap threshold - float dist = (m_RigidBody3D.position - m_TargetSyncPosition).magnitude; - if (dist > snapThreshold) - { - m_RigidBody3D.position = m_TargetSyncPosition; - m_RigidBody3D.velocity = m_TargetSyncVelocity; - } - - // handle no rotation interpolation - if (interpolateRotation == 0 && syncRotationAxis != AxisSyncMode.None) - { - m_RigidBody3D.rotation = m_TargetSyncRotation3D; - if (syncSpin) - { - m_RigidBody3D.angularVelocity = m_TargetSyncAngularVelocity3D; - } - } - - // handle no movement interpolation - if (m_InterpolateMovement == 0) - { - m_RigidBody3D.position = m_TargetSyncPosition; - } - - if (initialState && syncRotationAxis != AxisSyncMode.None) - { - m_RigidBody3D.rotation = m_TargetSyncRotation3D; - } - } - - void UnserializeMode2D(NetworkReader reader, bool initialState) - { - if (hasAuthority) - { - // this component must read the data that the server wrote, even if it ignores it. - // otherwise the NetworkReader stream will still contain that data for the next component. - - // position - reader.ReadVector2(); - - // velocity - reader.ReadVector2(); - - if (syncRotationAxis != AxisSyncMode.None) - { - UnserializeRotation2D(reader, rotationSyncCompression); - } - - if (syncSpin) - { - UnserializeSpin2D(reader, rotationSyncCompression); - } - return; - } - - if (m_RigidBody2D == null) - return; - - if (isServer && m_ClientMoveCallback2D != null) - { - Vector2 pos = reader.ReadVector2(); - Vector2 vel = reader.ReadVector2(); - float rot = 0; - if (syncRotationAxis != AxisSyncMode.None) - { - rot = UnserializeRotation2D(reader, rotationSyncCompression); - } - - if (m_ClientMoveCallback2D(ref pos, ref vel, ref rot)) - { - m_TargetSyncPosition = pos; - m_TargetSyncVelocity = vel; - if (syncRotationAxis != AxisSyncMode.None) - { - m_TargetSyncRotation2D = rot; - } - } - else - { - // rejected by callback - return; - } - } - else - { - // position - m_TargetSyncPosition = reader.ReadVector2(); - - // velocity - m_TargetSyncVelocity = reader.ReadVector2(); - - // rotation - if (syncRotationAxis != AxisSyncMode.None) - { - m_TargetSyncRotation2D = UnserializeRotation2D(reader, rotationSyncCompression); - } - } - - // spin - if (syncSpin) - { - m_TargetSyncAngularVelocity2D = UnserializeSpin2D(reader, rotationSyncCompression); - } - - if (isServer && !isClient) - { - // dedicated server needs to apply immediately, there is no interpolation - transform.position = m_TargetSyncPosition; - m_RigidBody2D.MoveRotation(m_TargetSyncRotation2D); - m_RigidBody2D.velocity = m_TargetSyncVelocity; - return; - } - - // handle zero send rate - if (syncInterval == 0) - { - // NOTE: cannot use m_RigidBody2D.MovePosition() and set velocity in the same frame, so use transform.position - transform.position = m_TargetSyncPosition; - m_RigidBody2D.velocity = m_TargetSyncVelocity; - if (syncRotationAxis != AxisSyncMode.None) - { - m_RigidBody2D.MoveRotation(m_TargetSyncRotation2D); - } - if (syncSpin) - { - m_RigidBody2D.angularVelocity = m_TargetSyncAngularVelocity2D; - } - return; - } - - // handle position snap threshold - float dist = (m_RigidBody2D.position - (Vector2)m_TargetSyncPosition).magnitude; - if (dist > snapThreshold) - { - m_RigidBody2D.position = m_TargetSyncPosition; - m_RigidBody2D.velocity = m_TargetSyncVelocity; - } - - // handle no rotation interpolation - if (interpolateRotation == 0 && syncRotationAxis != AxisSyncMode.None) - { - m_RigidBody2D.rotation = m_TargetSyncRotation2D; - if (syncSpin) - { - m_RigidBody2D.angularVelocity = m_TargetSyncAngularVelocity2D; - } - } - - // handle no movement interpolation - if (m_InterpolateMovement == 0) - { - m_RigidBody2D.position = m_TargetSyncPosition; - } - - if (initialState) - { - m_RigidBody2D.rotation = m_TargetSyncRotation2D; - } - } - - void UnserializeModeCharacterController(NetworkReader reader, bool initialState) - { - if (hasAuthority) - { - // this component must read the data that the server wrote, even if it ignores it. - // otherwise the NetworkReader stream will still contain that data for the next component. - - // position - reader.ReadVector3(); - - if (syncRotationAxis != AxisSyncMode.None) - { - UnserializeRotation3D(reader, syncRotationAxis, rotationSyncCompression); - } - return; - } - - if (isServer && m_ClientMoveCallback3D != null) - { - var pos = reader.ReadVector3(); - Quaternion rot = Quaternion.identity; - if (syncRotationAxis != AxisSyncMode.None) - { - rot = UnserializeRotation3D(reader, syncRotationAxis, rotationSyncCompression); - } - - if (m_CharacterController == null) - return; - - // no velocity in packet, use current local velocity - var vel = m_CharacterController.velocity; - - if (m_ClientMoveCallback3D(ref pos, ref vel, ref rot)) - { - m_TargetSyncPosition = pos; - m_TargetSyncVelocity = vel; - if (syncRotationAxis != AxisSyncMode.None) - { - m_TargetSyncRotation3D = rot; - } - } - else - { - // rejected by callback - return; - } - } - else - { - // position - m_TargetSyncPosition = reader.ReadVector3(); - - // no velocity - - // rotation - if (syncRotationAxis != AxisSyncMode.None) - { - m_TargetSyncRotation3D = UnserializeRotation3D(reader, syncRotationAxis, rotationSyncCompression); - } - - // no spin - } - - if (m_CharacterController == null) - return; - - // total distance away the target position is - var totalDistToTarget = (m_TargetSyncPosition - transform.position); // 5 units - var perSecondDist = totalDistToTarget / syncInterval; - m_FixedPosDiff = perSecondDist * Time.fixedDeltaTime; - - if (isServer && !isClient) - { - // dedicated server needs to apply immediately, there is no interpolation - transform.position = m_TargetSyncPosition; - transform.rotation = m_TargetSyncRotation3D; - return; - } - - // handle zero send rate - if (syncInterval == 0) - { - transform.position = m_TargetSyncPosition; - //m_RigidBody3D.velocity = m_TargetSyncVelocity; - if (syncRotationAxis != AxisSyncMode.None) - { - transform.rotation = m_TargetSyncRotation3D; - } - return; - } - - // handle position snap threshold - float dist = (transform.position - m_TargetSyncPosition).magnitude; - if (dist > snapThreshold) - { - transform.position = m_TargetSyncPosition; - } - - // handle no rotation interpolation - if (interpolateRotation == 0 && syncRotationAxis != AxisSyncMode.None) - { - transform.rotation = m_TargetSyncRotation3D; - } - - // handle no movement interpolation - if (m_InterpolateMovement == 0) - { - transform.position = m_TargetSyncPosition; - } - - if (initialState && syncRotationAxis != AxisSyncMode.None) - { - transform.rotation = m_TargetSyncRotation3D; - } - } - - void FixedUpdate() - { - if (isServer) - { - FixedUpdateServer(); - } - if (isClient) - { - FixedUpdateClient(); - } - } - - void FixedUpdateServer() - { - if (syncVarDirtyBits != 0) - return; - - // dont run if network isn't active - if (!NetworkServer.active) - return; - - // dont run if we haven't been spawned yet - if (!isServer) - return; - - // dont' auto-dirty if no send interval - if (syncInterval == 0) - return; - - float distance = (transform.position - m_PrevPosition).magnitude; - if (distance < movementTheshold) - { - distance = Quaternion.Angle(m_PrevRotation, transform.rotation); - if (distance < movementTheshold) - { - if (!CheckVelocityChanged()) - { - return; - } - } - } - - // This will cause transform to be sent - SetDirtyBit(1); - } - - bool CheckVelocityChanged() - { - if (transformSyncMode == TransformSyncMode.SyncRigidbody2D) - { - if (m_RigidBody2D && m_VelocityThreshold > 0) - { - return Mathf.Abs(m_RigidBody2D.velocity.sqrMagnitude - m_PrevVelocity) >= m_VelocityThreshold; - } - } - else if (transformSyncMode == TransformSyncMode.SyncRigidbody3D) - { - if (m_RigidBody3D && m_VelocityThreshold > 0) - { - return Mathf.Abs(m_RigidBody3D.velocity.sqrMagnitude - m_PrevVelocity) >= m_VelocityThreshold; - } - } - return false; - } - - void FixedUpdateClient() - { - // dont run if we haven't received any sync data - if (m_LastClientSyncTime == 0) - return; - - // dont run if network isn't active - if (!NetworkServer.active && !NetworkClient.active) - return; - - // dont run if we haven't been spawned yet - if (!isServer && !isClient) - return; - - // dont run if not expecting continuous updates - if (syncInterval == 0) - return; - - // dont run this if this client has authority over this player object - if (hasAuthority) - return; - - // interpolate on client - switch (transformSyncMode) - { - case TransformSyncMode.SyncRigidbody3D: - InterpolateTransformMode3D(); - break; - case TransformSyncMode.SyncRigidbody2D: - InterpolateTransformMode2D(); - break; - case TransformSyncMode.SyncCharacterController: - InterpolateTransformModeCharacterController(); - break; - } - } - - void InterpolateTransformMode3D() - { - if (m_InterpolateMovement != 0) - { - Vector3 newVelocity = (m_TargetSyncPosition - m_RigidBody3D.position) * m_InterpolateMovement / syncInterval; - m_RigidBody3D.velocity = newVelocity; - } - - if (interpolateRotation != 0) - { - m_RigidBody3D.MoveRotation(Quaternion.Slerp( - m_RigidBody3D.rotation, - m_TargetSyncRotation3D, - Time.fixedDeltaTime * interpolateRotation)); - - //m_TargetSyncRotation3D *= Quaternion.Euler(m_TargetSyncAngularVelocity3D * Time.fixedDeltaTime); - - // move sync rotation slightly in rotation direction - //m_TargetSyncRotation3D += (m_TargetSyncAngularVelocity3D * Time.fixedDeltaTime * moveAheadRatio); - } - - // move sync position slightly in the position of velocity - m_TargetSyncPosition += (m_TargetSyncVelocity * Time.fixedDeltaTime * k_MoveAheadRatio); - } - - void InterpolateTransformModeCharacterController() - { - if (m_FixedPosDiff == Vector3.zero && m_TargetSyncRotation3D == transform.rotation) - return; - - if (m_InterpolateMovement != 0) - { - m_CharacterController.Move(m_FixedPosDiff * m_InterpolateMovement); - } - - if (interpolateRotation != 0) - { - transform.rotation = Quaternion.Slerp( - transform.rotation, - m_TargetSyncRotation3D, - Time.fixedDeltaTime * interpolateRotation * 10); - } - if (Time.time - m_LastClientSyncTime > syncInterval) - { - // turn off interpolation if we go out of the time window for a new packet - m_FixedPosDiff = Vector3.zero; - - var diff = m_TargetSyncPosition - transform.position; - m_CharacterController.Move(diff); - } - } - - void InterpolateTransformMode2D() - { - if (m_InterpolateMovement != 0) - { - Vector2 oldVelocity = m_RigidBody2D.velocity; - Vector2 newVelocity = ((Vector2)m_TargetSyncPosition - m_RigidBody2D.position) * m_InterpolateMovement / syncInterval; - if (!m_Grounded && newVelocity.y < 0) - { - newVelocity.y = oldVelocity.y; - } - m_RigidBody2D.velocity = newVelocity; - } - - if (interpolateRotation != 0) - { - Quaternion newRotation = Quaternion.Slerp( - transform.rotation, - Quaternion.Euler(0, 0, m_TargetSyncRotation2D), - Time.fixedDeltaTime * interpolateRotation / syncInterval); - - m_RigidBody2D.MoveRotation(newRotation.eulerAngles.z); - - // move sync rotation slightly in rotation direction - m_TargetSyncRotation2D += (m_TargetSyncAngularVelocity2D * Time.fixedDeltaTime * k_MoveAheadRatio); - } - - // move sync position slightly in the position of velocity - m_TargetSyncPosition += (m_TargetSyncVelocity * Time.fixedDeltaTime * k_MoveAheadRatio); - } - - // --------------------- local transform sync ------------------------ - - void Update() - { - if (!hasAuthority) - return; - - if (!localPlayerAuthority) - return; - - if (NetworkServer.active) - return; - - if (Time.time - m_LastClientSendTime > syncInterval) - { - SendTransform(); - m_LastClientSendTime = Time.time; - } - } - - bool HasMoved() - { - float diff = 0; - - // check if position has changed - if (m_RigidBody3D != null) - { - diff = (m_RigidBody3D.position - m_PrevPosition).magnitude; - } - else if (m_RigidBody2D != null) - { - diff = (m_RigidBody2D.position - (Vector2)m_PrevPosition).magnitude; - } - else - { - diff = (transform.position - m_PrevPosition).magnitude; - } - - if (diff > k_LocalMovementThreshold) - { - return true; - } - - // check if rotation has changed - if (m_RigidBody3D != null) - { - diff = Quaternion.Angle(m_RigidBody3D.rotation, m_PrevRotation); - } - else if (m_RigidBody2D != null) - { - diff = Math.Abs(m_RigidBody2D.rotation - m_PrevRotation2D); - } - else - { - diff = Quaternion.Angle(transform.rotation, m_PrevRotation); - } - if (diff > k_LocalRotationThreshold) - { - return true; - } - - // check if velocty has changed - if (m_RigidBody3D != null) - { - diff = Mathf.Abs(m_RigidBody3D.velocity.sqrMagnitude - m_PrevVelocity); - } - else if (m_RigidBody2D != null) - { - diff = Mathf.Abs(m_RigidBody2D.velocity.sqrMagnitude - m_PrevVelocity); - } - - return diff > k_LocalVelocityThreshold; - } - - [Client] - void SendTransform() - { - if (!HasMoved() || ClientScene.readyConnection == null) - { - return; - } - - NetworkWriter writer = new NetworkWriter(); - switch (transformSyncMode) - { - case TransformSyncMode.SyncNone: - return; - case TransformSyncMode.SyncTransform: - SerializeModeTransform(writer); - break; - case TransformSyncMode.SyncRigidbody3D: - SerializeMode3D(writer); - break; - case TransformSyncMode.SyncRigidbody2D: - SerializeMode2D(writer); - break; - case TransformSyncMode.SyncCharacterController: - SerializeModeCharacterController(writer); - break; - } - - if (m_RigidBody3D != null) - { - m_PrevPosition = m_RigidBody3D.position; - m_PrevRotation = m_RigidBody3D.rotation; - m_PrevVelocity = m_RigidBody3D.velocity.sqrMagnitude; - } - else if (m_RigidBody2D != null) - { - m_PrevPosition = m_RigidBody2D.position; - m_PrevRotation2D = m_RigidBody2D.rotation; - m_PrevVelocity = m_RigidBody2D.velocity.sqrMagnitude; - } - else - { - m_PrevPosition = transform.position; - m_PrevRotation = transform.rotation; - } - - LocalPlayerTransformMessage message = new LocalPlayerTransformMessage(); - message.netId = netId; - message.payload = writer.ToArray(); - - ClientScene.readyConnection.Send((short)MsgType.LocalPlayerTransform, message); - } - - public static void HandleTransform(NetworkMessage netMsg) - { - LocalPlayerTransformMessage message = netMsg.ReadMessage(); - - GameObject foundObj = NetworkServer.FindLocalObject(message.netId); - if (foundObj == null) - { - Debug.LogError("Received NetworkTransform data for GameObject that doesn't exist"); - return; - } - NetworkTransform foundSync = foundObj.GetComponent(); - if (foundSync == null) - { - Debug.LogError("HandleTransform null target"); - return; - } - if (!foundSync.localPlayerAuthority) - { - Debug.LogError("HandleTransform no localPlayerAuthority"); - return; - } - if (netMsg.conn.clientOwnedObjects == null) - { - Debug.LogError("HandleTransform object not owned by connection"); - return; - } - - if (netMsg.conn.clientOwnedObjects.Contains(message.netId)) - { - NetworkReader reader = new NetworkReader(message.payload); - switch (foundSync.transformSyncMode) - { - case TransformSyncMode.SyncNone: - return; - case TransformSyncMode.SyncTransform: - foundSync.UnserializeModeTransform(reader, false); - break; - case TransformSyncMode.SyncRigidbody3D: - foundSync.UnserializeMode3D(reader, false); - break; - case TransformSyncMode.SyncRigidbody2D: - foundSync.UnserializeMode2D(reader, false); - break; - case TransformSyncMode.SyncCharacterController: - foundSync.UnserializeModeCharacterController(reader, false); - break; - } - foundSync.m_LastClientSyncTime = Time.time; - return; - } - - Debug.LogWarning("HandleTransform netId:" + message.netId + " is not for a valid player"); - } - - // --------------------- Compression Helper functions ------------------------ - - static void WriteAngle(NetworkWriter writer, float angle, CompressionSyncMode compression) - { - switch (compression) - { - case CompressionSyncMode.None: - writer.Write(angle); - break; - case CompressionSyncMode.Low: - writer.Write((short)(angle / (2 * Math.PI) * short.MaxValue)); - break; - case CompressionSyncMode.High: - writer.Write((byte)(angle / (2 * Math.PI) * byte.MaxValue)); - break; - } - } - - static float ReadAngle(NetworkReader reader, CompressionSyncMode compression) - { - switch (compression) - { - case CompressionSyncMode.None: - return reader.ReadSingle(); - case CompressionSyncMode.Low: - return reader.ReadInt16() * (float)(2 * Math.PI / short.MaxValue); - case CompressionSyncMode.High: - return reader.ReadByte() * (float)(2 * Math.PI / byte.MaxValue); - } - return 0; - } - - // --------------------- Serialization Helper functions ------------------------ - - public static void SerializeVelocity3D(NetworkWriter writer, Vector3 velocity, CompressionSyncMode compression) - { - writer.Write(velocity); - } - - public static void SerializeVelocity2D(NetworkWriter writer, Vector2 velocity, CompressionSyncMode compression) - { - writer.Write(velocity); - } - - public static void SerializeRotation3D(NetworkWriter writer, Quaternion rot, AxisSyncMode mode, CompressionSyncMode compression) - { - switch (mode) - { - case AxisSyncMode.None: - break; - - case AxisSyncMode.AxisX: - WriteAngle(writer, rot.eulerAngles.x, compression); - break; - - case AxisSyncMode.AxisY: - WriteAngle(writer, rot.eulerAngles.y, compression); - break; - - case AxisSyncMode.AxisZ: - WriteAngle(writer, rot.eulerAngles.z, compression); - break; - - case AxisSyncMode.AxisXY: - WriteAngle(writer, rot.eulerAngles.x, compression); - WriteAngle(writer, rot.eulerAngles.y, compression); - break; - - case AxisSyncMode.AxisXZ: - WriteAngle(writer, rot.eulerAngles.x, compression); - WriteAngle(writer, rot.eulerAngles.z, compression); - break; - - case AxisSyncMode.AxisYZ: - WriteAngle(writer, rot.eulerAngles.y, compression); - WriteAngle(writer, rot.eulerAngles.z, compression); - break; - - case AxisSyncMode.AxisXYZ: - WriteAngle(writer, rot.eulerAngles.x, compression); - WriteAngle(writer, rot.eulerAngles.y, compression); - WriteAngle(writer, rot.eulerAngles.z, compression); - break; - } - } - - public static void SerializeRotation2D(NetworkWriter writer, float rot, CompressionSyncMode compression) - { - WriteAngle(writer, rot, compression); - } - - public static void SerializeSpin3D(NetworkWriter writer, Vector3 angularVelocity, AxisSyncMode mode, CompressionSyncMode compression) - { - switch (mode) - { - case AxisSyncMode.None: - break; - - case AxisSyncMode.AxisX: - WriteAngle(writer, angularVelocity.x, compression); - break; - - case AxisSyncMode.AxisY: - WriteAngle(writer, angularVelocity.y, compression); - break; - - case AxisSyncMode.AxisZ: - WriteAngle(writer, angularVelocity.z, compression); - break; - - case AxisSyncMode.AxisXY: - WriteAngle(writer, angularVelocity.x, compression); - WriteAngle(writer, angularVelocity.y, compression); - break; - - case AxisSyncMode.AxisXZ: - WriteAngle(writer, angularVelocity.x, compression); - WriteAngle(writer, angularVelocity.z, compression); - break; - - case AxisSyncMode.AxisYZ: - WriteAngle(writer, angularVelocity.y, compression); - WriteAngle(writer, angularVelocity.z, compression); - break; - - case AxisSyncMode.AxisXYZ: - WriteAngle(writer, angularVelocity.x, compression); - WriteAngle(writer, angularVelocity.y, compression); - WriteAngle(writer, angularVelocity.z, compression); - break; - } - } - - public static void SerializeSpin2D(NetworkWriter writer, float angularVelocity, CompressionSyncMode compression) - { - WriteAngle(writer, angularVelocity, compression); - } - - public static Vector3 UnserializeVelocity3D(NetworkReader reader, CompressionSyncMode compression) - { - return reader.ReadVector3(); - } - - public static Vector3 UnserializeVelocity2D(NetworkReader reader, CompressionSyncMode compression) - { - return reader.ReadVector2(); - } - - public static Quaternion UnserializeRotation3D(NetworkReader reader, AxisSyncMode mode, CompressionSyncMode compression) - { - Quaternion rotation = Quaternion.identity; - Vector3 rotv = Vector3.zero; - - switch (mode) - { - case AxisSyncMode.None: - break; - - case AxisSyncMode.AxisX: - rotv.Set(ReadAngle(reader, compression), 0, 0); - rotation.eulerAngles = rotv; - break; - - case AxisSyncMode.AxisY: - rotv.Set(0, ReadAngle(reader, compression), 0); - rotation.eulerAngles = rotv; - break; - - case AxisSyncMode.AxisZ: - rotv.Set(0, 0, ReadAngle(reader, compression)); - rotation.eulerAngles = rotv; - break; - - case AxisSyncMode.AxisXY: - rotv.Set(ReadAngle(reader, compression), ReadAngle(reader, compression), 0); - rotation.eulerAngles = rotv; - break; - - case AxisSyncMode.AxisXZ: - rotv.Set(ReadAngle(reader, compression), 0, ReadAngle(reader, compression)); - rotation.eulerAngles = rotv; - break; - - case AxisSyncMode.AxisYZ: - rotv.Set(0, ReadAngle(reader, compression), ReadAngle(reader, compression)); - rotation.eulerAngles = rotv; - break; - - case AxisSyncMode.AxisXYZ: - rotv.Set(ReadAngle(reader, compression), ReadAngle(reader, compression), ReadAngle(reader, compression)); - rotation.eulerAngles = rotv; - break; - } - return rotation; - } - - public static float UnserializeRotation2D(NetworkReader reader, CompressionSyncMode compression) - { - return ReadAngle(reader, compression); - } - - public static Vector3 UnserializeSpin3D(NetworkReader reader, AxisSyncMode mode, CompressionSyncMode compression) - { - Vector3 spin = Vector3.zero; - switch (mode) - { - case AxisSyncMode.None: - break; - - case AxisSyncMode.AxisX: - spin.Set(ReadAngle(reader, compression), 0, 0); - break; - - case AxisSyncMode.AxisY: - spin.Set(0, ReadAngle(reader, compression), 0); - break; - - case AxisSyncMode.AxisZ: - spin.Set(0, 0, ReadAngle(reader, compression)); - break; - - case AxisSyncMode.AxisXY: - spin.Set(ReadAngle(reader, compression), ReadAngle(reader, compression), 0); - break; - - case AxisSyncMode.AxisXZ: - spin.Set(ReadAngle(reader, compression), 0, ReadAngle(reader, compression)); - break; - - case AxisSyncMode.AxisYZ: - spin.Set(0, ReadAngle(reader, compression), ReadAngle(reader, compression)); - break; - - case AxisSyncMode.AxisXYZ: - spin.Set(ReadAngle(reader, compression), ReadAngle(reader, compression), ReadAngle(reader, compression)); - break; - } - return spin; - } - - public static float UnserializeSpin2D(NetworkReader reader, CompressionSyncMode compression) - { - return ReadAngle(reader, compression); - } - - public override void OnStartAuthority() - { - // must reset this timer, or the server will continue to send target position instead of current position - m_LastClientSyncTime = 0; - } + protected override Transform targetComponent { get { return transform; } } } -} +} \ No newline at end of file diff --git a/Mirror/Runtime/NetworkTransformBase.cs b/Mirror/Runtime/NetworkTransformBase.cs new file mode 100644 index 0000000000..18555fa187 --- /dev/null +++ b/Mirror/Runtime/NetworkTransformBase.cs @@ -0,0 +1,458 @@ +// vis2k: +// base class for NetworkTransform and NetworkTransformChild. +// New method is simple and stupid. No more 1500 lines of code. +// +// Server sends current data. +// Client saves it and interpolates last and latest data points. +// Update handles transform movement / rotation +// FixedUpdate handles rigidbody movement / rotation +// +// Notes: +// * Built-in Teleport detection in case of lags / teleport / obstacles +// * Quaternion > EulerAngles because gimbal lock and Quaternion.Slerp +// * Syncs XYZ. Works 3D and 2D. Saving 4 bytes isn't worth 1000 lines of code. +// * Initial delay might happen if server sends packet immediately after moving +// just 1cm, hence we move 1cm and then wait 100ms for next packet +// * Only way for smooth movement is to use a fixed movement speed during +// interpolation. interpolation over time is never that good. +// +using System; +using UnityEngine; + +namespace Mirror +{ + public abstract class NetworkTransformBase : NetworkBehaviour + { + // rotation compression. not public so that other scripts can't modify + // it at runtime. alternatively we could send 1 extra byte for the mode + // each time so clients know how to decompress, but the whole point was + // to save bandwidth in the first place. + // -> can still be modified in the Inspector while the game is running, + // but would cause errors immediately and be pretty obvious. + [Tooltip("Compresses 16 Byte Quaternion into None=12, Some=6, Much=3, Lots=2 Byte")] + [SerializeField] Compression compressRotation = Compression.Much; + public enum Compression { None, Much, Lots }; // easily understandable and funny + + // server + Vector3 lastPosition; + Quaternion lastRotation; + + // client + public class DataPoint + { + public float timeStamp; + public Vector3 position; + public Quaternion rotation; + public float movementSpeed; + } + // interpolation start and goal + DataPoint start; + DataPoint goal; + + // local authority send time + float lastClientSendTime; + + // target transform to sync. can be on a child. + protected abstract Transform targetComponent { get; } + + // one GameObject might have multiple NetworkTransform/Child components. We need to know the index to properly + // assign the MsgType.LocalPlayerTransform message. + int componentIndex; + + void Awake() + { + // TODO use componentId instead? + componentIndex = Array.IndexOf(GetComponents(), this); + } + + // serialization is needed by OnSerialize and by manual sending from authority + static void SerializeIntoWriter(NetworkWriter writer, Vector3 position, Quaternion rotation, Compression compressRotation) + { + // serialize position + writer.Write(position); + + // serialize rotation + // writing quaternion = 16 byte + // writing euler angles = 12 byte + // -> quaternion->euler->quaternion always works. + // -> gimbal lock only occurs when adding. + Vector3 euler = rotation.eulerAngles; + if (compressRotation == Compression.None) + { + // write 3 floats = 12 byte + writer.Write(euler.x); + writer.Write(euler.y); + writer.Write(euler.z); + } + else if (compressRotation == Compression.Much) + { + // write 3 byte. scaling [0,360] to [0,255] + writer.Write(Utils.ScaleFloatToByte(euler.x, 0, 360, byte.MinValue, byte.MaxValue)); + writer.Write(Utils.ScaleFloatToByte(euler.y, 0, 360, byte.MinValue, byte.MaxValue)); + writer.Write(Utils.ScaleFloatToByte(euler.z, 0, 360, byte.MinValue, byte.MaxValue)); + } + else if (compressRotation == Compression.Lots) + { + // write 2 byte, 5 bits for each float + writer.Write(Utils.PackThreeFloatsIntoUShort(euler.x, euler.y, euler.z, 0, 360)); + } + } + + public override bool OnSerialize(NetworkWriter writer, bool initialState) + { + SerializeIntoWriter(writer, targetComponent.transform.position, targetComponent.transform.rotation, compressRotation); + return true; + } + + // try to estimate movement speed for a data point based on how far it + // moved since the previous one + // => if this is the first time ever then we use our best guess: + // -> delta based on transform.position + // -> elapsed based on send interval hoping that it roughly matches + static float EstimateMovementSpeed(DataPoint from, DataPoint to, Transform transform, float sendInterval) + { + Vector3 delta = to.position - (from != null ? from.position : transform.position); + float elapsed = from != null ? to.timeStamp - from.timeStamp : sendInterval; + return elapsed > 0 ? delta.magnitude / elapsed : 0; // avoid NaN + } + + // serialization is needed by OnSerialize and by manual sending from authority + void DeserializeFromReader(NetworkReader reader) + { + // put it into a data point immediately + DataPoint temp = new DataPoint(); + + // deserialize position + temp.position = reader.ReadVector3(); + + // deserialize rotation + if (compressRotation == Compression.None) + { + // read 3 floats = 16 byte + float x = reader.ReadSingle(); + float y = reader.ReadSingle(); + float z = reader.ReadSingle(); + temp.rotation = Quaternion.Euler(x, y, z); + } + else if (compressRotation == Compression.Much) + { + // read 3 byte. scaling [0,255] to [0,360] + float x = Utils.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360); + float y = Utils.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360); + float z = Utils.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360); + temp.rotation = Quaternion.Euler(x, y, z); + } + else if (compressRotation == Compression.Lots) + { + // read 2 byte, 5 bits per float + float[] xyz = Utils.UnpackUShortIntoThreeFloats(reader.ReadUInt16(), 0, 360); + temp.rotation = Quaternion.Euler(xyz[0], xyz[1], xyz[2]); + } + + // timestamp + temp.timeStamp = Time.time; + + // movement speed: based on how far it moved since last time + // has to be calculated before 'start' is overwritten + temp.movementSpeed = EstimateMovementSpeed(goal, temp, targetComponent.transform, syncInterval); + + // reassign start wisely + // -> first ever data point? then make something up for previous one + // so that we can start interpolation without waiting for next. + if (start == null) + { + start = new DataPoint{ + timeStamp=Time.time - syncInterval, + position=targetComponent.transform.position, + rotation=targetComponent.transform.rotation, + movementSpeed=temp.movementSpeed + }; + } + // -> second or nth data point? then update previous, but: + // we start at where ever we are right now, so that it's + // perfectly smooth and we don't jump anywhere + // + // example if we are at 'x': + // + // A--x->B + // + // and then receive a new point C: + // + // A--x--B + // | + // | + // C + // + // then we don't want to just jump to B and start interpolation: + // + // x + // | + // | + // C + // + // we stay at 'x' and interpolate from there to C: + // + // x..B + // \ . + // \. + // C + // + else + { + float oldDistance = Vector3.Distance(start.position, goal.position); + float newDistance = Vector3.Distance(goal.position, temp.position); + + start = goal; + + // teleport / lag / obstacle detection: only continue at current + // position if we aren't too far away + if (Vector3.Distance(targetComponent.transform.position, start.position) < oldDistance + newDistance) + { + start.position = targetComponent.transform.position; + start.rotation = targetComponent.transform.rotation; + } + } + + // set new destination in any case. new data is best data. + goal = temp; + } + + public override void OnDeserialize(NetworkReader reader, bool initialState) + { + // deserialize + DeserializeFromReader(reader); + } + + // local authority client sends sync message to server for broadcasting + // note: message is registered in NetworkServer.RegisterMessageHandlers + // because internal messages can't be registered from the outside + // TODO make this a [Command] later (Weaver can't weave this as long as + // it's still in the same DLL) + public static void OnClientToServerSync(NetworkMessage netMsg) + { + // read message + TransformMessage message = netMsg.ReadMessage(); + + // find that NetworkIdentity + NetworkIdentity identity; + if (!NetworkIdentity.spawned.TryGetValue(message.netId, out identity)) + { + Debug.LogError("Received NetworkTransform data for GameObject that doesn't exist"); + return; + } + + NetworkTransformBase[] foundSyncs = identity.GetComponents(); + if (foundSyncs == null || foundSyncs.Length == 0 || message.componentIndex > foundSyncs.Length - 1) + { + Debug.LogError("HandleTransform null target"); + return; + } + + NetworkTransformBase foundSync = foundSyncs[message.componentIndex]; + if (!foundSync.localPlayerAuthority) + { + Debug.LogError("HandleTransform no localPlayerAuthority"); + return; + } + + if (netMsg.conn.clientOwnedObjects == null) + { + Debug.LogError("HandleTransform object not owned by connection"); + return; + } + + if (netMsg.conn.clientOwnedObjects.Contains(message.netId)) + { + // deserialize payload + NetworkReader reader = new NetworkReader(message.payload); + foundSync.DeserializeFromReader(reader); + + // server-only mode does no interpolation to save computations, + // but let's set the position directly + if (foundSync.isServer && !foundSync.isClient) + foundSync.ApplyPositionAndRotation(foundSync.goal.position, foundSync.goal.rotation); + + // set dirty so that OnSerialize broadcasts it + foundSync.SetDirtyBit(1UL); + } + else + { + Debug.LogWarning("HandleTransform netId:" + message.netId + " is not for a valid player"); + } + } + + // where are we in the timeline between start and goal? [0,1] + static float CurrentInterpolationFactor(DataPoint start, DataPoint goal) + { + if (start != null) + { + float difference = goal.timeStamp - start.timeStamp; + + // the moment we get 'goal', 'start' is supposed to + // start, so elapsed time is based on: + float elapsed = Time.time - goal.timeStamp; + return difference > 0 ? elapsed / difference : 0; // avoid NaN + } + return 0; + } + + static Vector3 InterpolatePosition(DataPoint start, DataPoint goal, Vector3 currentPosition) + { + if (start != null) + { + // Option 1: simply interpolate based on time. but stutter + // will happen, it's not that smooth. especially noticeable if + // the camera automatically follows the player + // float t = CurrentInterpolationFactor(); + // return Vector3.Lerp(start.position, goal.position, t); + + // Option 2: always += speed + // -> speed is 0 if we just started after idle, so always use max + // for best results + float speed = Mathf.Max(start.movementSpeed, goal.movementSpeed); + return Vector3.MoveTowards(currentPosition, goal.position, speed * Time.deltaTime); + } + return currentPosition; + } + + static Quaternion InterpolateRotation(DataPoint start, DataPoint goal, Quaternion defaultRotation) + { + if (start != null) + { + float t = CurrentInterpolationFactor(start, goal); + return Quaternion.Slerp(start.rotation, goal.rotation, t); + } + return defaultRotation; + } + + // teleport / lag / stuck detection + // -> checking distance is not enough since there could be just a tiny + // fence between us and the goal + // -> checking time always works, this way we just teleport if we still + // didn't reach the goal after too much time has elapsed + bool NeedsTeleport() + { + // calculate time between the two data points + float startTime = start != null ? start.timeStamp : Time.time - syncInterval; + float goalTime = goal != null ? goal.timeStamp : Time.time; + float difference = goalTime - startTime; + float timeSinceGoalReceived = Time.time - goalTime; + return timeSinceGoalReceived > difference * 5; + } + + // moved since last time we checked it? + bool HasMovedOrRotated() + { + // moved or rotated? + bool moved = lastPosition != targetComponent.transform.position; + bool rotated = lastRotation != targetComponent.transform.rotation; + + // save last for next frame to compare + lastPosition = targetComponent.transform.position; + lastRotation = targetComponent.transform.rotation; + + return moved || rotated; + } + + // set position carefully depending on the target component + void ApplyPositionAndRotation(Vector3 position, Quaternion rotation) + { + targetComponent.transform.position = position; + targetComponent.transform.rotation = rotation; + } + + void Update() + { + // if server then always sync to others. + if (isServer) + { + // just use OnSerialize via SetDirtyBit only sync when position + // changed. set dirty bits 0 or 1 + SetDirtyBit(HasMovedOrRotated() ? 1UL : 0UL); + } + + // no 'else if' since host mode would be both + if (isClient) + { + // send to server if we have local authority (and aren't the server) + // -> only if connectionToServer has been initialized yet too + if (!isServer && hasAuthority && connectionToServer != null) + { + // check only each 'syncInterval' + if (Time.time - lastClientSendTime >= syncInterval) + { + if (HasMovedOrRotated()) + { + // serialize + NetworkWriter writer = new NetworkWriter(); + SerializeIntoWriter(writer, targetComponent.transform.position, targetComponent.transform.rotation, compressRotation); + + // send message to server + TransformMessage message = new TransformMessage(); + message.netId = netId; + message.componentIndex = componentIndex; + message.payload = writer.ToArray(); + connectionToServer.Send((short)MsgType.LocalPlayerTransform, message); + } + lastClientSendTime = Time.time; + } + } + + // apply interpolation on client for all players + // except for local player if he has authority and handles it himself + if (!(isLocalPlayer && hasAuthority)) + { + // received one yet? (initialized?) + if (goal != null) + { + // teleport or interpolate + if (NeedsTeleport()) + { + ApplyPositionAndRotation(goal.position, goal.rotation); + } + else + { + ApplyPositionAndRotation(InterpolatePosition(start, goal, targetComponent.transform.position), + InterpolateRotation(start, goal, targetComponent.transform.rotation)); + } + } + } + } + } + + static void DrawDataPointGizmo(DataPoint data, Color color) + { + // use a little offset because transform.position might be in + // the ground in many cases + Vector3 offset = Vector3.up * 0.01f; + + // draw position + Gizmos.color = color; + Gizmos.DrawSphere(data.position + offset, 0.5f); + + // draw forward and up + Gizmos.color = Color.blue; // like unity move tool + Gizmos.DrawRay(data.position + offset, data.rotation * Vector3.forward); + + Gizmos.color = Color.green; // like unity move tool + Gizmos.DrawRay(data.position + offset, data.rotation * Vector3.up); + } + + static void DrawLineBetweenDataPoints(DataPoint data1, DataPoint data2, Color color) + { + Gizmos.color = Color.white; + Gizmos.DrawLine(data1.position, data2.position); + } + + // draw the data points for easier debugging + void OnDrawGizmos() + { + // draw start and goal points + if (start != null) DrawDataPointGizmo(start, Color.gray); + if (goal != null) DrawDataPointGizmo(goal, Color.white); + + // draw line between them + if (start != null && goal != null) DrawLineBetweenDataPoints(start, goal, Color.cyan); + } + } +} diff --git a/Mirror/Runtime/NetworkTransformChild.cs b/Mirror/Runtime/NetworkTransformChild.cs index cc4b29e41a..b8080357f5 100644 --- a/Mirror/Runtime/NetworkTransformChild.cs +++ b/Mirror/Runtime/NetworkTransformChild.cs @@ -1,427 +1,11 @@ -using System; using UnityEngine; namespace Mirror { [AddComponentMenu("Network/NetworkTransformChild")] - public class NetworkTransformChild : NetworkBehaviour + public class NetworkTransformChild : NetworkTransformBase { - [SerializeField] - Transform m_Target; - - [SerializeField] - uint m_ChildIndex; - - NetworkTransform m_Root; - - [SerializeField] NetworkTransform.AxisSyncMode m_SyncRotationAxis = NetworkTransform.AxisSyncMode.AxisXYZ; - [SerializeField] NetworkTransform.CompressionSyncMode m_RotationSyncCompression = NetworkTransform.CompressionSyncMode.None; - [SerializeField] float m_MovementThreshold = 0.001f; - - [SerializeField] float m_InterpolateRotation = 0.5f; - [SerializeField] float m_InterpolateMovement = 0.5f; - [SerializeField] NetworkTransform.ClientMoveCallback3D m_ClientMoveCallback3D; - - // movement smoothing - Vector3 m_TargetSyncPosition; - Quaternion m_TargetSyncRotation3D; - - float m_LastClientSyncTime; // last time client received a sync from server - float m_LastClientSendTime; // last time client send a sync to server - - Vector3 m_PrevPosition; - Quaternion m_PrevRotation; - - const float k_LocalMovementThreshold = 0.00001f; - const float k_LocalRotationThreshold = 0.00001f; - - // settings - public Transform target { get {return m_Target; } set { m_Target = value; OnValidate(); } } - public uint childIndex { get { return m_ChildIndex; }} - public NetworkTransform.AxisSyncMode syncRotationAxis { get { return m_SyncRotationAxis; } set { m_SyncRotationAxis = value; } } - public NetworkTransform.CompressionSyncMode rotationSyncCompression { get { return m_RotationSyncCompression; } set { m_RotationSyncCompression = value; } } - public float movementThreshold { get { return m_MovementThreshold; } set { m_MovementThreshold = value; } } - public float interpolateRotation { get { return m_InterpolateRotation; } set { m_InterpolateRotation = value; } } - public float interpolateMovement { get { return m_InterpolateMovement; } set { m_InterpolateMovement = value; } } - public NetworkTransform.ClientMoveCallback3D clientMoveCallback3D { get { return m_ClientMoveCallback3D; } set { m_ClientMoveCallback3D = value; } } - - // runtime data - public float lastSyncTime { get { return m_LastClientSyncTime; } } - public Vector3 targetSyncPosition { get { return m_TargetSyncPosition; } } - public Quaternion targetSyncRotation3D { get { return m_TargetSyncRotation3D; } } - - void OnValidate() - { - // root parent of target must have a NetworkTransform - if (m_Target != null) - { - Transform parent = m_Target.parent; - if (parent == null) - { - Debug.LogError("NetworkTransformChild target cannot be the root transform."); - m_Target = null; - return; - } - while (parent.parent != null) - { - parent = parent.parent; - } - - m_Root = parent.gameObject.GetComponent(); - if (m_Root == null) - { - Debug.LogError("NetworkTransformChild root must have NetworkTransform"); - m_Target = null; - return; - } - } - - if (m_Root != null) - { - // childIndex is the index within all the NetworkChildTransforms on the root - m_ChildIndex = UInt32.MaxValue; - var childTransforms = m_Root.GetComponents(); - for (uint i = 0; i < childTransforms.Length; i++) - { - if (childTransforms[i] == this) - { - m_ChildIndex = i; - break; - } - } - if (m_ChildIndex == UInt32.MaxValue) - { - Debug.LogError("NetworkTransformChild component must be a child in the same hierarchy"); - m_Target = null; - } - } - - if (m_SyncRotationAxis < NetworkTransform.AxisSyncMode.None || m_SyncRotationAxis > NetworkTransform.AxisSyncMode.AxisXYZ) - { - m_SyncRotationAxis = NetworkTransform.AxisSyncMode.None; - } - - if (movementThreshold < 0) - { - movementThreshold = 0.00f; - } - - if (interpolateRotation < 0) - { - interpolateRotation = 0.01f; - } - if (interpolateRotation > 1.0f) - { - interpolateRotation = 1.0f; - } - - if (interpolateMovement < 0) - { - interpolateMovement = 0.01f; - } - if (interpolateMovement > 1.0f) - { - interpolateMovement = 1.0f; - } - } - - void Awake() - { - m_PrevPosition = m_Target.localPosition; - m_PrevRotation = m_Target.localRotation; - } - - public override bool OnSerialize(NetworkWriter writer, bool initialState) - { - if (initialState) - { - // always write initial state, no dirty bits - } - else if (syncVarDirtyBits == 0) - { - writer.WritePackedUInt32(0); - return false; - } - else - { - // dirty bits - writer.WritePackedUInt32(1); - } - - SerializeModeTransform(writer); - return true; - } - - void SerializeModeTransform(NetworkWriter writer) - { - // position - writer.Write(m_Target.localPosition); - - // rotation - if (m_SyncRotationAxis != NetworkTransform.AxisSyncMode.None) - { - NetworkTransform.SerializeRotation3D(writer, m_Target.localRotation, syncRotationAxis, rotationSyncCompression); - } - m_PrevPosition = m_Target.localPosition; - m_PrevRotation = m_Target.localRotation; - } - - public override void OnDeserialize(NetworkReader reader, bool initialState) - { - if (isServer && NetworkServer.localClientActive) - return; - - if (!initialState) - { - if (reader.ReadPackedUInt32() == 0) - return; - } - UnserializeModeTransform(reader, initialState); - - m_LastClientSyncTime = Time.time; - } - - void UnserializeModeTransform(NetworkReader reader, bool initialState) - { - if (hasAuthority) - { - // this component must read the data that the server wrote, even if it ignores it. - // otherwise the NetworkReader stream will still contain that data for the next component. - - // position - reader.ReadVector3(); - - if (syncRotationAxis != NetworkTransform.AxisSyncMode.None) - { - NetworkTransform.UnserializeRotation3D(reader, syncRotationAxis, rotationSyncCompression); - } - return; - } - - if (isServer && m_ClientMoveCallback3D != null) - { - var pos = reader.ReadVector3(); - var vel = Vector3.zero; - var rot = Quaternion.identity; - if (syncRotationAxis != NetworkTransform.AxisSyncMode.None) - { - rot = NetworkTransform.UnserializeRotation3D(reader, syncRotationAxis, rotationSyncCompression); - } - - if (m_ClientMoveCallback3D(ref pos, ref vel, ref rot)) - { - m_TargetSyncPosition = pos; - if (syncRotationAxis != NetworkTransform.AxisSyncMode.None) - { - m_TargetSyncRotation3D = rot; - } - } - } - else - { - // position - m_TargetSyncPosition = reader.ReadVector3(); - - // rotation - if (syncRotationAxis != NetworkTransform.AxisSyncMode.None) - { - m_TargetSyncRotation3D = NetworkTransform.UnserializeRotation3D(reader, syncRotationAxis, rotationSyncCompression); - } - } - } - - void FixedUpdate() - { - if (isServer) - { - FixedUpdateServer(); - } - if (isClient) - { - FixedUpdateClient(); - } - } - - void FixedUpdateServer() - { - if (syncVarDirtyBits != 0) - return; - - // dont run if network isn't active - if (!NetworkServer.active) - return; - - // dont run if we haven't been spawned yet - if (!isServer) - return; - - // dont' auto-dirty if no send interval - if (syncInterval == 0) - return; - - float distance = (m_Target.localPosition - m_PrevPosition).sqrMagnitude; - if (distance < movementThreshold) - { - distance = Quaternion.Angle(m_PrevRotation, m_Target.localRotation); - if (distance < movementThreshold) - { - return; - } - } - - // This will cause transform to be sent - SetDirtyBit(1); - } - - void FixedUpdateClient() - { - // dont run if we haven't received any sync data - if (m_LastClientSyncTime == 0) - return; - - // dont run if network isn't active - if (!NetworkServer.active && !NetworkClient.active) - return; - - // dont run if we haven't been spawned yet - if (!isServer && !isClient) - return; - - // dont run if not expecting continuous updates - if (syncInterval == 0) - return; - - // dont run this if this client has authority over this player object - if (hasAuthority) - return; - - // interpolate on client - if (m_LastClientSyncTime != 0) - { - m_Target.localPosition = m_InterpolateMovement > 0 - ? Vector3.Lerp(m_Target.localPosition, m_TargetSyncPosition, m_InterpolateMovement) - : m_TargetSyncPosition; - - m_Target.localRotation = m_InterpolateRotation > 0 - ? Quaternion.Slerp(m_Target.localRotation, m_TargetSyncRotation3D, m_InterpolateRotation) - : m_TargetSyncRotation3D; - } - } - - // --------------------- local transform sync ------------------------ - - void Update() - { - if (!hasAuthority) - return; - - if (!localPlayerAuthority) - return; - - if (NetworkServer.active) - return; - - if (Time.time - m_LastClientSendTime > syncInterval) - { - SendTransform(); - m_LastClientSendTime = Time.time; - } - } - - bool HasMoved() - { - float diff = 0; - - // check if position has changed - diff = (m_Target.localPosition - m_PrevPosition).sqrMagnitude; - if (diff > k_LocalMovementThreshold) - { - return true; - } - - // check if rotation has changed - diff = Quaternion.Angle(m_Target.localRotation, m_PrevRotation); - if (diff > k_LocalRotationThreshold) - { - return true; - } - - // check if velocty has changed - - return false; - } - - [Client] - void SendTransform() - { - if (!HasMoved() || ClientScene.readyConnection == null) - { - return; - } - - NetworkWriter writer = new NetworkWriter(); - SerializeModeTransform(writer); - - LocalChildTransformMessage message = new LocalChildTransformMessage(); - message.netId = netId; - message.childIndex = m_ChildIndex; - message.payload = writer.ToArray(); - - m_PrevPosition = m_Target.localPosition; - m_PrevRotation = m_Target.localRotation; - - ClientScene.readyConnection.Send((short)MsgType.LocalChildTransform, message); - } - - internal static void HandleChildTransform(NetworkMessage netMsg) - { - LocalChildTransformMessage message = netMsg.ReadMessage(); - - GameObject foundObj = NetworkServer.FindLocalObject(message.netId); - if (foundObj == null) - { - Debug.LogError("Received NetworkTransformChild data for GameObject that doesn't exist"); - return; - } - var children = foundObj.GetComponents(); - if (children == null || children.Length == 0) - { - Debug.LogError("HandleChildTransform no children"); - return; - } - if (message.childIndex >= children.Length) - { - Debug.LogError("HandleChildTransform childIndex invalid"); - return; - } - - NetworkTransformChild foundSync = children[message.childIndex]; - if (foundSync == null) - { - Debug.LogError("HandleChildTransform null target"); - return; - } - if (!foundSync.localPlayerAuthority) - { - Debug.LogError("HandleChildTransform no localPlayerAuthority"); - return; - } - - if (!netMsg.conn.clientOwnedObjects.Contains(message.netId)) - { - Debug.LogWarning("NetworkTransformChild netId:" + message.netId + " is not for a valid player"); - return; - } - - foundSync.UnserializeModeTransform(new NetworkReader(message.payload), false); - foundSync.m_LastClientSyncTime = Time.time; - - if (!foundSync.isClient) - { - // dedicated server wont interpolate, so snap. - foundSync.m_Target.localPosition = foundSync.m_TargetSyncPosition; - foundSync.m_Target.localRotation = foundSync.m_TargetSyncRotation3D; - } - } + public Transform target; + protected override Transform targetComponent { get { return target; } } } } diff --git a/Mirror/Runtime/UNetwork.cs b/Mirror/Runtime/UNetwork.cs index e39dab25f4..6070190c86 100644 --- a/Mirror/Runtime/UNetwork.cs +++ b/Mirror/Runtime/UNetwork.cs @@ -39,7 +39,6 @@ public enum MsgType : short SpawnFinished = 12, ObjectHide = 13, LocalClientAuthority = 15, - LocalChildTransform = 16, // public system messages - can be replaced by user code Connect = 32, @@ -131,4 +130,60 @@ public static bool UnpackMessage(byte[] message, out ushort msgType, out byte[] return true; } } + + public static class Utils + { + // ScaleFloatToByte( -1f, -1f, 1f, byte.MinValue, byte.MaxValue) => 0 + // ScaleFloatToByte( 0f, -1f, 1f, byte.MinValue, byte.MaxValue) => 127 + // ScaleFloatToByte(0.5f, -1f, 1f, byte.MinValue, byte.MaxValue) => 191 + // ScaleFloatToByte( 1f, -1f, 1f, byte.MinValue, byte.MaxValue) => 255 + public static byte ScaleFloatToByte(float value, float minValue, float maxValue, byte minTarget, byte maxTarget) + { + // note: C# byte - byte => int, hence so many casts + int targetRange = maxTarget - minTarget; // max byte - min byte only fits into something bigger + float valueRange = maxValue - minValue; + float valueRelative = value - minValue; + return (byte)(minTarget + (byte)(valueRelative/valueRange * (float)targetRange)); + } + + // ScaleByteToFloat( 0, byte.MinValue, byte.MaxValue, -1, 1) => -1 + // ScaleByteToFloat(127, byte.MinValue, byte.MaxValue, -1, 1) => -0.003921569 + // ScaleByteToFloat(191, byte.MinValue, byte.MaxValue, -1, 1) => 0.4980392 + // ScaleByteToFloat(255, byte.MinValue, byte.MaxValue, -1, 1) => 1 + public static float ScaleByteToFloat(byte value, byte minValue, byte maxValue, float minTarget, float maxTarget) + { + // note: C# byte - byte => int, hence so many casts + float targetRange = maxTarget - minTarget; + byte valueRange = (byte)(maxValue - minValue); + byte valueRelative = (byte)(value - minValue); + return minTarget + ((float)valueRelative/(float)valueRange * targetRange); + } + + // eulerAngles have 3 floats, putting them into 2 bytes of [x,y],[z,0] + // would be a waste. instead we compress into 5 bits each => 15 bits. + // so a ushort. + public static ushort PackThreeFloatsIntoUShort(float u, float v, float w, float minValue, float maxValue) + { + // 5 bits max value = 1+2+4+8+16 = 31 = 0x1F + byte lower = ScaleFloatToByte(u, minValue, maxValue, 0x00, 0x1F); + byte middle = ScaleFloatToByte(v, minValue, maxValue, 0x00, 0x1F); + byte upper = ScaleFloatToByte(w, minValue, maxValue, 0x00, 0x1F); + ushort combined = (ushort)(upper << 10 | middle << 5 | lower); + return combined; + } + + // see PackThreeFloatsIntoUShort for explanation + public static float[] UnpackUShortIntoThreeFloats(ushort combined, float minTarget, float maxTarget) + { + byte lower = (byte)(combined & 0x1F); + byte middle = (byte)((combined >> 5) & 0x1F); + byte upper = (byte)(combined >> 10); // nothing on the left, no & needed + + // note: we have to use 4 bits per float, so between 0x00 and 0x0F + float u = ScaleByteToFloat(lower, 0x00, 0x1F, minTarget, maxTarget); + float v = ScaleByteToFloat(middle, 0x00, 0x1F, minTarget, maxTarget); + float w = ScaleByteToFloat(upper, 0x00, 0x1F, minTarget, maxTarget); + return new float[]{u, v, w}; + } + } } diff --git a/Mirror/Tests/Mirror.Tests.csproj b/Mirror/Tests/Mirror.Tests.csproj index e51c670c6e..bb3e3b9ee4 100644 --- a/Mirror/Tests/Mirror.Tests.csproj +++ b/Mirror/Tests/Mirror.Tests.csproj @@ -56,6 +56,7 @@ + diff --git a/Mirror/Tests/UtilsTest.cs b/Mirror/Tests/UtilsTest.cs new file mode 100644 index 0000000000..37bdd40ee6 --- /dev/null +++ b/Mirror/Tests/UtilsTest.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +namespace Mirror +{ + [TestFixture] + public class UtilsTest + { + [Test] + public void TestScaleFloatToByte() + { + Assert.That(Utils.ScaleFloatToByte( -1f, -1f, 1f, byte.MinValue, byte.MaxValue), Is.EqualTo(0)); + Assert.That(Utils.ScaleFloatToByte( 0f, -1f, 1f, byte.MinValue, byte.MaxValue), Is.EqualTo(127)); + Assert.That(Utils.ScaleFloatToByte(0.5f, -1f, 1f, byte.MinValue, byte.MaxValue), Is.EqualTo(191)); + Assert.That(Utils.ScaleFloatToByte( 1f, -1f, 1f, byte.MinValue, byte.MaxValue), Is.EqualTo(255)); + } + + [Test] + public void ScaleByteToFloat() + { + Assert.That(Utils.ScaleByteToFloat( 0, byte.MinValue, byte.MaxValue, -1, 1), Is.EqualTo(-1).Within(0.0001f)); + Assert.That(Utils.ScaleByteToFloat(127, byte.MinValue, byte.MaxValue, -1, 1), Is.EqualTo(-0.003921569f).Within(0.0001f)); + Assert.That(Utils.ScaleByteToFloat(191, byte.MinValue, byte.MaxValue, -1, 1), Is.EqualTo(0.4980392f).Within(0.0001f)); + Assert.That(Utils.ScaleByteToFloat(255, byte.MinValue, byte.MaxValue, -1, 1), Is.EqualTo(1).Within(0.0001f)); + } + } +} \ No newline at end of file