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
|
||||
}
|
||||
|
||||
// keep Rigibody configuration in memory while the component is removed.
|
||||
// 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
|
||||
[RequireComponent(typeof(Rigidbody))]
|
||||
public class ForecastRigidbody : NetworkBehaviour
|
||||
{
|
||||
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.")]
|
||||
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!
|
||||
[Header("Debugging")]
|
||||
public bool debugColors = false;
|
||||
@ -167,13 +116,6 @@ public override void OnStartClient()
|
||||
// set Rigidbody as kinematic by default on clients.
|
||||
// it's only dynamic on the server, and while predicting on clients.
|
||||
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)
|
||||
@ -228,14 +170,12 @@ public void AddPredictedForceAtPosition(Vector3 force, Vector3 position, ForceMo
|
||||
|
||||
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
|
||||
|
||||
// collision checking is disabled while following.
|
||||
// enable while predicting again.
|
||||
predictedRigidbody.detectCollisions = true;
|
||||
|
||||
state = ForecastState.PREDICTING;
|
||||
#if UNITY_EDITOR // PERF: only access .material in Editor, as it may instantiate!
|
||||
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 know the time when our [Command] arrives on server: NetworkTime.predictedTime.
|
||||
predictionStartTime = NetworkTime.predictedTime; // !!! not .time !!!
|
||||
|
||||
OnBeginPrediction();
|
||||
// Debug.Log($"{name} BEGIN PREDICTING @ {predictionStartTime:F2}");
|
||||
}
|
||||
@ -263,24 +202,20 @@ protected void BeginBlending()
|
||||
}
|
||||
|
||||
protected void BeginFollowing()
|
||||
{
|
||||
// remove rigidbody until it's needed again later
|
||||
if (rigidbodyOnDemand)
|
||||
{
|
||||
rbConfig = new RigidbodyConfiguration(predictedRigidbody);
|
||||
Destroy(predictedRigidbody);
|
||||
}
|
||||
else
|
||||
{
|
||||
predictedRigidbody.isKinematic = true; // full transform sync
|
||||
}
|
||||
|
||||
state = ForecastState.FOLLOWING;
|
||||
#if UNITY_EDITOR // PERF: only access .material in Editor, as it may instantiate!
|
||||
if (debugColors) rend.material.color = originalColor;
|
||||
#endif
|
||||
// reset the collision chain depth so it starts at 0 again next time
|
||||
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();
|
||||
// Debug.Log($"{name} BEGIN FOLLOW");
|
||||
}
|
||||
@ -319,14 +254,22 @@ protected virtual bool IsMoving() =>
|
||||
predictedRigidbody.angularVelocity.sqrMagnitude >= angularVelocitySensitivitySqr;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// Prediction always has a Rigidbody.
|
||||
// position changes should always happen in FixedUpdate, even while kinematic.
|
||||
// improves benchmark performance from 600 -> 870 FPS.
|
||||
void FixedUpdateClient()
|
||||
// Prediction uses a Rigidbody, which would suggest position changes to happen in FixedUpdate().
|
||||
// However, snapshot interpolation needs to run in Update() in order to look perfectly smooth.
|
||||
void UpdateClient()
|
||||
{
|
||||
// PREDICTING checks state, which happens in update()
|
||||
if (state == ForecastState.PREDICTING)
|
||||
@ -413,12 +356,7 @@ void FixedUpdateClient()
|
||||
Quaternion newRotation = Quaternion.Slerp(currentRotation, targetRotation, p).normalized;
|
||||
|
||||
// assign position and rotation together. faster than accessing manually.
|
||||
// in theory we must always set rigidbody.position/rotation instead of transform:
|
||||
// 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);
|
||||
tf.SetPositionAndRotation(newPosition, newRotation);
|
||||
|
||||
// transition to FOLLOWING after blending is done.
|
||||
// we could check 'if p >= 1' but if the user's curve never
|
||||
@ -455,25 +393,11 @@ void FixedUpdateClient()
|
||||
|
||||
// interpolate & apply to transform.
|
||||
TransformSnapshot computed = TransformSnapshot.Interpolate(from, to, t);
|
||||
if (rigidbodyOnDemand)
|
||||
{
|
||||
// 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
|
||||
void OnDrawGizmos()
|
||||
@ -528,8 +452,6 @@ void OnCollisionEnter(Collision collision)
|
||||
}
|
||||
|
||||
// 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 OnBeginBlending() {}
|
||||
protected virtual void OnBeginFollow() {}
|
||||
@ -569,7 +491,6 @@ void OnReceivedState(ForecastRigidbodyState data)//, bool sleeping)
|
||||
|
||||
// send state to clients every sendInterval.
|
||||
// reliable for now.
|
||||
// TODO we should use the one from FixedUpdate
|
||||
public override void OnSerialize(NetworkWriter writer, bool initialState)
|
||||
{
|
||||
// Time.time was at the beginning of this frame.
|
||||
@ -643,32 +564,5 @@ protected override void OnValidate()
|
||||
// force syncDirection to be 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);
|
||||
if (Physics.Raycast(ray, out RaycastHit hit))
|
||||
{
|
||||
// we may have hit the ghost object.
|
||||
// find the original.
|
||||
if (ForecastRigidbody.IsPredicted(hit.collider, out ForecastRigidbody predicted))
|
||||
if (hit.collider.TryGetComponent(out ForecastRigidbody predicted))
|
||||
{
|
||||
// apply force in a random direction, this looks best
|
||||
Debug.Log($"Applying force to: {hit.collider.name}");
|
||||
|
Loading…
Reference in New Issue
Block a user