From 96af64f4360c5540010fafac498c04b79a54bb7a Mon Sep 17 00:00:00 2001 From: mischa Date: Thu, 4 Apr 2024 00:52:23 +0800 Subject: [PATCH] fix recursive chain activations 'soft deadlock' --- .../ForecastRigidbody/ForecastRigidbody.cs | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/Assets/Mirror/Components/ForecastRigidbody/ForecastRigidbody.cs b/Assets/Mirror/Components/ForecastRigidbody/ForecastRigidbody.cs index bd73903cf..cd46e9090 100644 --- a/Assets/Mirror/Components/ForecastRigidbody/ForecastRigidbody.cs +++ b/Assets/Mirror/Components/ForecastRigidbody/ForecastRigidbody.cs @@ -48,6 +48,9 @@ public class ForecastRigidbody : NetworkBehaviour [Header("Collision Chaining")] [Tooltip("Enable to have actively predicted Rigidbodies activate other Rigidbodies they collide with.")] public bool collisionChaining = true; + [Tooltip("If a player interacts with an object, it can recursively activate other objects it collides with.\nDepth is the chain link from A->B->C->etc., it doesn't mean the amount of A->B, A->C, etc.\nNeeds to be finite to avoid chain activations going on forever like A->B->C->A (easy to notice in the stacked prediction example.")] + public int maxCollisionChainingDepth = 2; // A->B->C is enough! + int remainingCollisionChainDepth; // motion smoothing happen on-demand, because it requires moving physics components to another GameObject. // this only starts at a given velocity and ends when stopped moving. @@ -102,8 +105,7 @@ public override void OnStartClient() predictedRigidbody.isKinematic = true; } - // client prediction API - public void AddPredictedForce(Vector3 force, ForceMode mode) + void AddPredictedForceInternal(Vector3 force, ForceMode mode) { // apply local force dampening. // client applies a bit less force than the server, so that @@ -116,6 +118,26 @@ public void AddPredictedForce(Vector3 force, ForceMode mode) predictedRigidbody.AddForce(force, mode); } + // client prediction API + public void AddPredictedForce(Vector3 force, ForceMode mode) + { + // player interacted with this object explicitly. + // restart the collision chain at max. + remainingCollisionChainDepth = maxCollisionChainingDepth; + + // add the predicted force + AddPredictedForceInternal(force, mode); + } + + void AddPredictedForceChain(Vector3 force, ForceMode mode, int newChainDepth) + { + // apply the collision chain depth + remainingCollisionChainDepth = newChainDepth; + + // add the predicted force + AddPredictedForceInternal(force, mode); + } + protected void BeginPredicting() { predictedRigidbody.isKinematic = false; // full physics sync @@ -144,6 +166,8 @@ protected void BeginFollowing() predictedRigidbody.isKinematic = true; // full transform sync state = ForecastState.FOLLOWING; if (debugColors) rend.material.color = originalColor; + // reset the collision chain depth so it starts at 0 again next time + remainingCollisionChainDepth = 0; OnBeginFollow(); // Debug.Log($"{name} BEGIN FOLLOW"); } @@ -317,6 +341,14 @@ void OnCollisionEnter(Collision collision) // is the other object already predicting? then don't call events again. if (other.state != ForecastState.FOLLOWING) return; + // collided with an object that has not yet been activate. + // should this object still activate other objects? + // for example, chain depth = 3 means: A->B->C. + // so C->D would not activate anymore. + // (we always need to check this only for inactivate objects, + // otherwise A->B->A->B etc. would reduce the chain depth forever). + if (remainingCollisionChainDepth <= 0) return; + // the other object is in FOLLOWING mode (kinematic). // PhysX will register the collision, but not add the collision force while kinematic. // we need to add the force manually, which will also begin predicting it. @@ -324,7 +356,9 @@ void OnCollisionEnter(Collision collision) // => we need to calculate the direction manually to always be A->B. Vector3 direction = other.transform.position - transform.position; Vector3 impulse = direction.normalized * collision.impulse.magnitude; - other.AddPredictedForce(impulse, ForceMode.Impulse); + + + other.AddPredictedForceChain(impulse, ForceMode.Impulse, remainingCollisionChainDepth - 1); } // optional user callbacks, in case people need to know about events.