PredictedRigidbody: sync & show remote.sleeping to easier debug objects coming to rest

This commit is contained in:
mischa 2024-03-24 11:51:50 +08:00 committed by MrGadget
parent 824712df9c
commit 867bf7c492
2 changed files with 32 additions and 9 deletions

View File

@ -19,6 +19,7 @@ namespace Mirror
public class PredictedRigidbody : NetworkBehaviour public class PredictedRigidbody : NetworkBehaviour
{ {
Transform tf; // this component is performance critical. cache .transform getter! Transform tf; // this component is performance critical. cache .transform getter!
Renderer rend;
// Prediction sometimes moves the Rigidbody to a ghost object. // Prediction sometimes moves the Rigidbody to a ghost object.
// .predictedRigidbody is always kept up to date to wherever the RB is. // .predictedRigidbody is always kept up to date to wherever the RB is.
@ -93,7 +94,8 @@ public class PredictedRigidbody : NetworkBehaviour
public bool reduceSendsWhileIdle = true; public bool reduceSendsWhileIdle = true;
[Header("Debugging")] [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. // Rigidbody & Collider are moved out into a separate object.
// this way the visual object can smoothly follow. // this way the visual object can smoothly follow.
@ -112,9 +114,12 @@ public class PredictedRigidbody : NetworkBehaviour
Quaternion initialRotation; Quaternion initialRotation;
// Vector3 initialScale; // don't change scale for now. causes issues with parenting. // Vector3 initialScale; // don't change scale for now. causes issues with parenting.
Color originalColor;
protected virtual void Awake() protected virtual void Awake()
{ {
tf = transform; tf = transform;
rend = GetComponent<Renderer>();
predictedRigidbody = GetComponent<Rigidbody>(); predictedRigidbody = GetComponent<Rigidbody>();
if (predictedRigidbody == null) throw new InvalidOperationException($"Prediction: {name} is missing a Rigidbody component."); if (predictedRigidbody == null) throw new InvalidOperationException($"Prediction: {name} is missing a Rigidbody component.");
predictedRigidbodyTransform = predictedRigidbody.transform; predictedRigidbodyTransform = predictedRigidbody.transform;
@ -133,6 +138,9 @@ protected virtual void Awake()
motionSmoothingVelocityThresholdSqr = motionSmoothingVelocityThreshold * motionSmoothingVelocityThreshold; motionSmoothingVelocityThresholdSqr = motionSmoothingVelocityThreshold * motionSmoothingVelocityThreshold;
motionSmoothingAngularVelocityThresholdSqr = motionSmoothingAngularVelocityThreshold * motionSmoothingAngularVelocityThreshold; motionSmoothingAngularVelocityThresholdSqr = motionSmoothingAngularVelocityThreshold * motionSmoothingAngularVelocityThreshold;
positionCorrectionThresholdSqr = positionCorrectionThreshold * positionCorrectionThreshold; positionCorrectionThresholdSqr = positionCorrectionThreshold * positionCorrectionThreshold;
// renderer
originalColor = rend.material.color;
} }
protected virtual void CopyRenderersAsGhost(GameObject destination, Material material) 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. // process a received server state.
// compares it against our history and applies corrections if needed. // 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 // always update remote state ghost
if (remoteCopy != null) if (remoteCopy != null)
@ -672,6 +680,12 @@ void OnReceivedState(double timestamp, RigidbodyState state)
remoteCopyTransform.localScale = tf.lossyScale; // world scale! see CreateGhosts comment. 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, // performance: get Rigidbody position & rotation only once,
// and together via its transform // and together via its transform
predictedRigidbodyTransform.GetPositionAndRotation(out Vector3 physicsPosition, out Quaternion physicsRotation); predictedRigidbodyTransform.GetPositionAndRotation(out Vector3 physicsPosition, out Quaternion physicsRotation);
@ -830,7 +844,13 @@ public override void OnSerialize(NetworkWriter writer, bool initialState)
// writer.WriteVector3(predictedRigidbody.angularVelocity); // writer.WriteVector3(predictedRigidbody.angularVelocity);
// performance optimization: write a whole struct at once via blittable: // 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); writer.WritePredictedSyncData(data);
} }
@ -855,6 +875,7 @@ public override void OnDeserialize(NetworkReader reader, bool initialState)
Quaternion rotation = data.rotation; Quaternion rotation = data.rotation;
Vector3 velocity = data.velocity; Vector3 velocity = data.velocity;
Vector3 angularVelocity = data.angularVelocity; Vector3 angularVelocity = data.angularVelocity;
bool sleeping = data.sleeping != 0;
// server sends state at the end of the frame. // server sends state at the end of the frame.
// parse and apply the server's delta time to our timestamp. // 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; if (oneFrameAhead) timestamp += serverDeltaTime;
// process received state // 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() protected override void OnValidate()

View File

@ -22,15 +22,17 @@ public struct PredictedSyncData
public Quaternion rotation; // 16 bytes (word aligned) public Quaternion rotation; // 16 bytes (word aligned)
public Vector3 velocity; // 12 bytes (word aligned) public Vector3 velocity; // 12 bytes (word aligned)
public Vector3 angularVelocity; // 12 bytes (word aligned) public Vector3 angularVelocity; // 12 bytes (word aligned)
public byte sleeping; // 1 byte: bool isn't blittable
// constructor for convenience // 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.deltaTime = deltaTime;
this.position = position; this.position = position;
this.rotation = rotation; this.rotation = rotation;
this.velocity = velocity; this.velocity = velocity;
this.angularVelocity = angularVelocity; this.angularVelocity = angularVelocity;
this.sleeping = sleeping ? (byte)1 : (byte)0;
} }
} }