mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
update to latest changes
This commit is contained in:
parent
cd6e67a78e
commit
3f0dbadf34
@ -17,51 +17,7 @@ enum ForecastState
|
|||||||
FOLLOWING, // 100% server sided physics, client only follows .transform
|
FOLLOWING, // 100% server sided physics, client only follows .transform
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep Rigibody configuration in memory while the component is removed.
|
[RequireComponent(typeof(Rigidbody))]
|
||||||
// this way we can add the same one later.
|
|
||||||
struct RigidbodyConfiguration
|
|
||||||
{
|
|
||||||
public float mass;
|
|
||||||
public float drag;
|
|
||||||
public float angularDrag;
|
|
||||||
public bool useGravity;
|
|
||||||
public bool isKinematic;
|
|
||||||
public RigidbodyInterpolation interpolation;
|
|
||||||
public CollisionDetectionMode collisionDetectionMode;
|
|
||||||
public RigidbodyConstraints constraints;
|
|
||||||
public float sleepThreshold;
|
|
||||||
public bool freezeRotation;
|
|
||||||
|
|
||||||
public RigidbodyConfiguration(Rigidbody rb)
|
|
||||||
{
|
|
||||||
this.mass = rb.mass;
|
|
||||||
this.drag = rb.drag;
|
|
||||||
this.angularDrag = rb.angularDrag;
|
|
||||||
this.useGravity = rb.useGravity;
|
|
||||||
this.isKinematic = rb.isKinematic;
|
|
||||||
this.interpolation = rb.interpolation;
|
|
||||||
this.collisionDetectionMode = rb.collisionDetectionMode;
|
|
||||||
this.constraints = rb.constraints;
|
|
||||||
this.sleepThreshold = rb.sleepThreshold;
|
|
||||||
this.freezeRotation = rb.freezeRotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ApplyTo(Rigidbody rb)
|
|
||||||
{
|
|
||||||
rb.mass = mass;
|
|
||||||
rb.drag = drag;
|
|
||||||
rb.angularDrag = angularDrag;
|
|
||||||
rb.useGravity = useGravity;
|
|
||||||
rb.isKinematic = isKinematic;
|
|
||||||
rb.interpolation = interpolation;
|
|
||||||
rb.collisionDetectionMode = collisionDetectionMode;
|
|
||||||
rb.constraints = constraints;
|
|
||||||
rb.sleepThreshold = sleepThreshold;
|
|
||||||
rb.freezeRotation = freezeRotation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// [RequireComponent(typeof(Rigidbody))] <- RB is only kept on demand
|
|
||||||
public class ForecastRigidbody : NetworkBehaviour
|
public class ForecastRigidbody : NetworkBehaviour
|
||||||
{
|
{
|
||||||
Transform tf; // this component is performance critical. cache .transform getter!
|
Transform tf; // this component is performance critical. cache .transform getter!
|
||||||
@ -118,13 +74,6 @@ public class ForecastRigidbody : NetworkBehaviour
|
|||||||
[Tooltip("Reduce sends while velocity==0. Client's objects may slightly move due to gravity/physics, so we still want to send corrections occasionally even if an object is idle on the server the whole time.")]
|
[Tooltip("Reduce sends while velocity==0. Client's objects may slightly move due to gravity/physics, so we still want to send corrections occasionally even if an object is idle on the server the whole time.")]
|
||||||
public bool reduceSendsWhileIdle = true;
|
public bool reduceSendsWhileIdle = true;
|
||||||
|
|
||||||
[Header("Performance")]
|
|
||||||
// Rigidbody is kept only while predicting & blending.
|
|
||||||
// it's automatically removed while following to reduce any physics overhead.
|
|
||||||
[Tooltip("Option to only keep the Rigidbody while predicting. Automatically destroyed while following.\nUseful for massive physics scenes where the client only ever predicts a few Rigidbodies at a time.")]
|
|
||||||
public bool rigidbodyOnDemand = false;
|
|
||||||
RigidbodyConfiguration rbConfig;
|
|
||||||
|
|
||||||
#if UNITY_EDITOR // PERF: only access .material in Editor, as it may instantiate!
|
#if UNITY_EDITOR // PERF: only access .material in Editor, as it may instantiate!
|
||||||
[Header("Debugging")]
|
[Header("Debugging")]
|
||||||
public bool debugColors = false;
|
public bool debugColors = false;
|
||||||
@ -167,13 +116,6 @@ public override void OnStartClient()
|
|||||||
// set Rigidbody as kinematic by default on clients.
|
// set Rigidbody as kinematic by default on clients.
|
||||||
// it's only dynamic on the server, and while predicting on clients.
|
// it's only dynamic on the server, and while predicting on clients.
|
||||||
predictedRigidbody.isKinematic = true;
|
predictedRigidbody.isKinematic = true;
|
||||||
|
|
||||||
// copy Rigidbody settings and remove on clients until it's needed.
|
|
||||||
if (rigidbodyOnDemand)
|
|
||||||
{
|
|
||||||
rbConfig = new RigidbodyConfiguration(predictedRigidbody);
|
|
||||||
Destroy(predictedRigidbody);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddPredictedForceInternal(Vector3 force, ForceMode mode)
|
void AddPredictedForceInternal(Vector3 force, ForceMode mode)
|
||||||
@ -228,14 +170,12 @@ public void AddPredictedForceAtPosition(Vector3 force, Vector3 position, ForceMo
|
|||||||
|
|
||||||
protected void BeginPredicting()
|
protected void BeginPredicting()
|
||||||
{
|
{
|
||||||
// add Rigidbody component on demand while predicting
|
|
||||||
if (rigidbodyOnDemand && predictedRigidbody == null)
|
|
||||||
{
|
|
||||||
predictedRigidbody = gameObject.AddComponent<Rigidbody>();
|
|
||||||
rbConfig.ApplyTo(predictedRigidbody);
|
|
||||||
}
|
|
||||||
|
|
||||||
predictedRigidbody.isKinematic = false; // full physics sync
|
predictedRigidbody.isKinematic = false; // full physics sync
|
||||||
|
|
||||||
|
// collision checking is disabled while following.
|
||||||
|
// enable while predicting again.
|
||||||
|
predictedRigidbody.detectCollisions = true;
|
||||||
|
|
||||||
state = ForecastState.PREDICTING;
|
state = ForecastState.PREDICTING;
|
||||||
#if UNITY_EDITOR // PERF: only access .material in Editor, as it may instantiate!
|
#if UNITY_EDITOR // PERF: only access .material in Editor, as it may instantiate!
|
||||||
if (debugColors) rend.material.color = predictingColor;
|
if (debugColors) rend.material.color = predictingColor;
|
||||||
@ -243,7 +183,6 @@ protected void BeginPredicting()
|
|||||||
// we want to predict until the first server state for our [Command] AddForce came in.
|
// we want to predict until the first server state for our [Command] AddForce came in.
|
||||||
// we know the time when our [Command] arrives on server: NetworkTime.predictedTime.
|
// we know the time when our [Command] arrives on server: NetworkTime.predictedTime.
|
||||||
predictionStartTime = NetworkTime.predictedTime; // !!! not .time !!!
|
predictionStartTime = NetworkTime.predictedTime; // !!! not .time !!!
|
||||||
|
|
||||||
OnBeginPrediction();
|
OnBeginPrediction();
|
||||||
// Debug.Log($"{name} BEGIN PREDICTING @ {predictionStartTime:F2}");
|
// Debug.Log($"{name} BEGIN PREDICTING @ {predictionStartTime:F2}");
|
||||||
}
|
}
|
||||||
@ -264,23 +203,19 @@ protected void BeginBlending()
|
|||||||
|
|
||||||
protected void BeginFollowing()
|
protected void BeginFollowing()
|
||||||
{
|
{
|
||||||
// remove rigidbody until it's needed again later
|
predictedRigidbody.isKinematic = true; // full transform sync
|
||||||
if (rigidbodyOnDemand)
|
|
||||||
{
|
|
||||||
rbConfig = new RigidbodyConfiguration(predictedRigidbody);
|
|
||||||
Destroy(predictedRigidbody);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
predictedRigidbody.isKinematic = true; // full transform sync
|
|
||||||
}
|
|
||||||
|
|
||||||
state = ForecastState.FOLLOWING;
|
state = ForecastState.FOLLOWING;
|
||||||
#if UNITY_EDITOR // PERF: only access .material in Editor, as it may instantiate!
|
#if UNITY_EDITOR // PERF: only access .material in Editor, as it may instantiate!
|
||||||
if (debugColors) rend.material.color = originalColor;
|
if (debugColors) rend.material.color = originalColor;
|
||||||
#endif
|
#endif
|
||||||
// reset the collision chain depth so it starts at 0 again next time
|
// reset the collision chain depth so it starts at 0 again next time
|
||||||
remainingCollisionChainDepth = 0;
|
remainingCollisionChainDepth = 0;
|
||||||
|
|
||||||
|
// perf: setting kinematic rigidbody's positions still causes
|
||||||
|
// significant physics overhead on low end devices.
|
||||||
|
// try disable collisions while purely following.
|
||||||
|
predictedRigidbody.detectCollisions = false;
|
||||||
|
|
||||||
OnBeginFollow();
|
OnBeginFollow();
|
||||||
// Debug.Log($"{name} BEGIN FOLLOW");
|
// Debug.Log($"{name} BEGIN FOLLOW");
|
||||||
}
|
}
|
||||||
@ -319,14 +254,22 @@ protected virtual bool IsMoving() =>
|
|||||||
predictedRigidbody.angularVelocity.sqrMagnitude >= angularVelocitySensitivitySqr;
|
predictedRigidbody.angularVelocity.sqrMagnitude >= angularVelocitySensitivitySqr;
|
||||||
|
|
||||||
void Update()
|
void Update()
|
||||||
|
{
|
||||||
|
if (isClientOnly) UpdateClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
// NetworkTransform improvement: server broadcast needs to run in LateUpdate
|
||||||
|
// after other scripts may changed positions in Update().
|
||||||
|
// otherwise this may run before user update, delaying detection until next frame.
|
||||||
|
// this could cause visible jitter.
|
||||||
|
void LateUpdate()
|
||||||
{
|
{
|
||||||
if (isServer) UpdateServer();
|
if (isServer) UpdateServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prediction always has a Rigidbody.
|
// Prediction uses a Rigidbody, which would suggest position changes to happen in FixedUpdate().
|
||||||
// position changes should always happen in FixedUpdate, even while kinematic.
|
// However, snapshot interpolation needs to run in Update() in order to look perfectly smooth.
|
||||||
// improves benchmark performance from 600 -> 870 FPS.
|
void UpdateClient()
|
||||||
void FixedUpdateClient()
|
|
||||||
{
|
{
|
||||||
// PREDICTING checks state, which happens in update()
|
// PREDICTING checks state, which happens in update()
|
||||||
if (state == ForecastState.PREDICTING)
|
if (state == ForecastState.PREDICTING)
|
||||||
@ -413,12 +356,7 @@ void FixedUpdateClient()
|
|||||||
Quaternion newRotation = Quaternion.Slerp(currentRotation, targetRotation, p).normalized;
|
Quaternion newRotation = Quaternion.Slerp(currentRotation, targetRotation, p).normalized;
|
||||||
|
|
||||||
// assign position and rotation together. faster than accessing manually.
|
// assign position and rotation together. faster than accessing manually.
|
||||||
// in theory we must always set rigidbody.position/rotation instead of transform:
|
tf.SetPositionAndRotation(newPosition, newRotation);
|
||||||
// https://forum.unity.com/threads/how-expensive-is-physics-synctransforms.1366146/#post-9557491
|
|
||||||
// however, tf.SetPositionAndRotation is faster in our Prediction Benchmark.
|
|
||||||
predictedRigidbody.position = newPosition;
|
|
||||||
predictedRigidbody.rotation = newRotation;
|
|
||||||
// tf.SetPositionAndRotation(newPosition, newRotation);
|
|
||||||
|
|
||||||
// transition to FOLLOWING after blending is done.
|
// transition to FOLLOWING after blending is done.
|
||||||
// we could check 'if p >= 1' but if the user's curve never
|
// we could check 'if p >= 1' but if the user's curve never
|
||||||
@ -455,25 +393,11 @@ void FixedUpdateClient()
|
|||||||
|
|
||||||
// interpolate & apply to transform.
|
// interpolate & apply to transform.
|
||||||
TransformSnapshot computed = TransformSnapshot.Interpolate(from, to, t);
|
TransformSnapshot computed = TransformSnapshot.Interpolate(from, to, t);
|
||||||
if (rigidbodyOnDemand)
|
tf.SetPositionAndRotation(computed.position, computed.rotation); // scale is ignored
|
||||||
{
|
|
||||||
// Rigidbody is destroyed while following.
|
|
||||||
tf.SetPositionAndRotation(computed.position, computed.rotation); // scale is ignored
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Rigidbody exists while following
|
|
||||||
predictedRigidbody.position = computed.position;
|
|
||||||
predictedRigidbody.rotation = computed.rotation;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FixedUpdate()
|
|
||||||
{
|
|
||||||
if (isClientOnly) FixedUpdateClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
#if UNITY_EDITOR // PERF: only run gizmos in editor
|
#if UNITY_EDITOR // PERF: only run gizmos in editor
|
||||||
void OnDrawGizmos()
|
void OnDrawGizmos()
|
||||||
@ -528,8 +452,6 @@ void OnCollisionEnter(Collision collision)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// optional user callbacks, in case people need to know about events.
|
// optional user callbacks, in case people need to know about events.
|
||||||
protected virtual void OnSnappedIntoPlace() {}
|
|
||||||
protected virtual void OnBeforeApplyState() {}
|
|
||||||
protected virtual void OnBeginPrediction() {}
|
protected virtual void OnBeginPrediction() {}
|
||||||
protected virtual void OnBeginBlending() {}
|
protected virtual void OnBeginBlending() {}
|
||||||
protected virtual void OnBeginFollow() {}
|
protected virtual void OnBeginFollow() {}
|
||||||
@ -569,7 +491,6 @@ void OnReceivedState(ForecastRigidbodyState data)//, bool sleeping)
|
|||||||
|
|
||||||
// send state to clients every sendInterval.
|
// send state to clients every sendInterval.
|
||||||
// reliable for now.
|
// reliable for now.
|
||||||
// TODO we should use the one from FixedUpdate
|
|
||||||
public override void OnSerialize(NetworkWriter writer, bool initialState)
|
public override void OnSerialize(NetworkWriter writer, bool initialState)
|
||||||
{
|
{
|
||||||
// Time.time was at the beginning of this frame.
|
// Time.time was at the beginning of this frame.
|
||||||
@ -643,32 +564,5 @@ protected override void OnValidate()
|
|||||||
// force syncDirection to be ServerToClient
|
// force syncDirection to be ServerToClient
|
||||||
syncDirection = SyncDirection.ServerToClient;
|
syncDirection = SyncDirection.ServerToClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function for Physics tests to check if a Rigidbody belongs to
|
|
||||||
// a ForecastRigidbody component (either on it, or on its ghost).
|
|
||||||
public static bool IsPredicted(Rigidbody rb, out ForecastRigidbody predictedRigidbody)
|
|
||||||
{
|
|
||||||
// by default, Rigidbody is on the ForecastRigidbody GameObject
|
|
||||||
if (rb.TryGetComponent(out predictedRigidbody))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// otherwise the Rigidbody does not belong to any ForecastRigidbody.
|
|
||||||
predictedRigidbody = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function for Physics tests to check if a Collider (which may be in children) belongs to
|
|
||||||
// a ForecastRigidbody component (either on it, or on its ghost).
|
|
||||||
public static bool IsPredicted(Collider co, out ForecastRigidbody predictedRigidbody)
|
|
||||||
{
|
|
||||||
// by default, Collider is on the ForecastRigidbody GameObject or it's children.
|
|
||||||
predictedRigidbody = co.GetComponentInParent<ForecastRigidbody>();
|
|
||||||
if (predictedRigidbody != null)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// otherwise the Rigidbody does not belong to any ForecastRigidbody.
|
|
||||||
predictedRigidbody = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,7 @@ void Update()
|
|||||||
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
|
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
|
||||||
if (Physics.Raycast(ray, out RaycastHit hit))
|
if (Physics.Raycast(ray, out RaycastHit hit))
|
||||||
{
|
{
|
||||||
// we may have hit the ghost object.
|
if (hit.collider.TryGetComponent(out ForecastRigidbody predicted))
|
||||||
// find the original.
|
|
||||||
if (ForecastRigidbody.IsPredicted(hit.collider, out ForecastRigidbody predicted))
|
|
||||||
{
|
{
|
||||||
// apply force in a random direction, this looks best
|
// apply force in a random direction, this looks best
|
||||||
Debug.Log($"Applying force to: {hit.collider.name}");
|
Debug.Log($"Applying force to: {hit.collider.name}");
|
||||||
|
Loading…
Reference in New Issue
Block a user