diff --git a/Assets/Mirror/Components/PredictedRigidbody/PredictedRigidbody.cs b/Assets/Mirror/Components/PredictedRigidbody/PredictedRigidbody.cs index a0e42a6d2..675c4e237 100644 --- a/Assets/Mirror/Components/PredictedRigidbody/PredictedRigidbody.cs +++ b/Assets/Mirror/Components/PredictedRigidbody/PredictedRigidbody.cs @@ -19,6 +19,7 @@ namespace Mirror public class PredictedRigidbody : NetworkBehaviour { Transform tf; // this component is performance critical. cache .transform getter! + Renderer rend; // Prediction sometimes moves the Rigidbody to a ghost object. // .predictedRigidbody is always kept up to date to wherever the RB is. @@ -93,7 +94,8 @@ public class PredictedRigidbody : NetworkBehaviour public bool reduceSendsWhileIdle = true; [Header("Debugging")] - public float lineTime = 10; + [Tooltip("Useful to debug objects not coming to rest. This color codes the local objects which are already asleep on the server.")] + public bool showRemoteSleeping = false; // Rigidbody & Collider are moved out into a separate object. // this way the visual object can smoothly follow. @@ -112,9 +114,12 @@ public class PredictedRigidbody : NetworkBehaviour Quaternion initialRotation; // Vector3 initialScale; // don't change scale for now. causes issues with parenting. + Color originalColor; + protected virtual void Awake() { tf = transform; + rend = GetComponent(); predictedRigidbody = GetComponent(); if (predictedRigidbody == null) throw new InvalidOperationException($"Prediction: {name} is missing a Rigidbody component."); predictedRigidbodyTransform = predictedRigidbody.transform; @@ -133,6 +138,9 @@ protected virtual void Awake() motionSmoothingVelocityThresholdSqr = motionSmoothingVelocityThreshold * motionSmoothingVelocityThreshold; motionSmoothingAngularVelocityThresholdSqr = motionSmoothingAngularVelocityThreshold * motionSmoothingAngularVelocityThreshold; positionCorrectionThresholdSqr = positionCorrectionThreshold * positionCorrectionThreshold; + + // renderer + originalColor = rend.material.color; } protected virtual void CopyRenderersAsGhost(GameObject destination, Material material) @@ -662,7 +670,7 @@ void ApplyState(double timestamp, Vector3 position, Quaternion rotation, Vector3 // process a received server state. // compares it against our history and applies corrections if needed. - void OnReceivedState(double timestamp, RigidbodyState state) + void OnReceivedState(double timestamp, RigidbodyState state, bool sleeping) { // always update remote state ghost if (remoteCopy != null) @@ -672,6 +680,12 @@ void OnReceivedState(double timestamp, RigidbodyState state) remoteCopyTransform.localScale = tf.lossyScale; // world scale! see CreateGhosts comment. } + // color code remote sleeping objects to debug objects coming to rest + if (showRemoteSleeping) + { + rend.material.color = sleeping ? Color.gray : originalColor; + } + // performance: get Rigidbody position & rotation only once, // and together via its transform predictedRigidbodyTransform.GetPositionAndRotation(out Vector3 physicsPosition, out Quaternion physicsRotation); @@ -830,7 +844,13 @@ public override void OnSerialize(NetworkWriter writer, bool initialState) // writer.WriteVector3(predictedRigidbody.angularVelocity); // performance optimization: write a whole struct at once via blittable: - PredictedSyncData data = new PredictedSyncData(Time.deltaTime, position, rotation, predictedRigidbody.velocity, predictedRigidbody.angularVelocity); + PredictedSyncData data = new PredictedSyncData( + Time.deltaTime, + position, + rotation, + predictedRigidbody.velocity, + predictedRigidbody.angularVelocity, + predictedRigidbody.IsSleeping()); writer.WritePredictedSyncData(data); } @@ -855,6 +875,7 @@ public override void OnDeserialize(NetworkReader reader, bool initialState) Quaternion rotation = data.rotation; Vector3 velocity = data.velocity; Vector3 angularVelocity = data.angularVelocity; + bool sleeping = data.sleeping != 0; // server sends state at the end of the frame. // parse and apply the server's delta time to our timestamp. @@ -868,7 +889,7 @@ public override void OnDeserialize(NetworkReader reader, bool initialState) if (oneFrameAhead) timestamp += serverDeltaTime; // process received state - OnReceivedState(timestamp, new RigidbodyState(timestamp, Vector3.zero, position, Quaternion.identity, rotation, Vector3.zero, velocity, Vector3.zero, angularVelocity)); + OnReceivedState(timestamp, new RigidbodyState(timestamp, Vector3.zero, position, Quaternion.identity, rotation, Vector3.zero, velocity, Vector3.zero, angularVelocity), sleeping); } protected override void OnValidate() diff --git a/Assets/Mirror/Components/PredictedRigidbody/PredictedSyncData.cs b/Assets/Mirror/Components/PredictedRigidbody/PredictedSyncData.cs index ed08c7787..ac4a9693e 100644 --- a/Assets/Mirror/Components/PredictedRigidbody/PredictedSyncData.cs +++ b/Assets/Mirror/Components/PredictedRigidbody/PredictedSyncData.cs @@ -17,20 +17,22 @@ namespace Mirror [StructLayout(LayoutKind.Sequential)] // explicitly force sequential public struct PredictedSyncData { - public float deltaTime; // 4 bytes (word aligned) - public Vector3 position; // 12 bytes (word aligned) - public Quaternion rotation; // 16 bytes (word aligned) - public Vector3 velocity; // 12 bytes (word aligned) + public float deltaTime; // 4 bytes (word aligned) + public Vector3 position; // 12 bytes (word aligned) + public Quaternion rotation; // 16 bytes (word aligned) + public Vector3 velocity; // 12 bytes (word aligned) public Vector3 angularVelocity; // 12 bytes (word aligned) + public byte sleeping; // 1 byte: bool isn't blittable // constructor for convenience - public PredictedSyncData(float deltaTime, Vector3 position, Quaternion rotation, Vector3 velocity, Vector3 angularVelocity) + public PredictedSyncData(float deltaTime, Vector3 position, Quaternion rotation, Vector3 velocity, Vector3 angularVelocity, bool sleeping) { this.deltaTime = deltaTime; this.position = position; this.rotation = rotation; this.velocity = velocity; this.angularVelocity = angularVelocity; + this.sleeping = sleeping ? (byte)1 : (byte)0; } }