mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
wip
This commit is contained in:
parent
2c04d50a3d
commit
bf9fe3880d
@ -10,6 +10,13 @@
|
|||||||
|
|
||||||
namespace Mirror
|
namespace Mirror
|
||||||
{
|
{
|
||||||
|
enum ForecastState
|
||||||
|
{
|
||||||
|
PREDICT, // 100% client side physics prediction
|
||||||
|
BLEND, // blending client prediction with server state
|
||||||
|
FOLLOW, // 100% server sided physics, client only follows .transform
|
||||||
|
}
|
||||||
|
|
||||||
[RequireComponent(typeof(Rigidbody))]
|
[RequireComponent(typeof(Rigidbody))]
|
||||||
public class ForecastRigidbody : NetworkBehaviour
|
public class ForecastRigidbody : NetworkBehaviour
|
||||||
{
|
{
|
||||||
@ -25,6 +32,7 @@ public class ForecastRigidbody : NetworkBehaviour
|
|||||||
|
|
||||||
[Header("Blending")]
|
[Header("Blending")]
|
||||||
[Range(0.01f, 1)] public float blendPerSync = 0.1f;
|
[Range(0.01f, 1)] public float blendPerSync = 0.1f;
|
||||||
|
ForecastState state = ForecastState.FOLLOW; // follow until the player interacts
|
||||||
|
|
||||||
// motion smoothing happen on-demand, because it requires moving physics components to another GameObject.
|
// 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.
|
// this only starts at a given velocity and ends when stopped moving.
|
||||||
@ -81,6 +89,10 @@ protected virtual void Awake()
|
|||||||
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;
|
||||||
|
|
||||||
|
// set Rigidbody as kinematic by default.
|
||||||
|
// it's only dynamic while predicting.
|
||||||
|
predictedRigidbody.isKinematic = true;
|
||||||
|
|
||||||
// in fast mode, we need to force enable Rigidbody.interpolation.
|
// in fast mode, we need to force enable Rigidbody.interpolation.
|
||||||
// otherwise there's not going to be any smoothing whatsoever.
|
// otherwise there's not going to be any smoothing whatsoever.
|
||||||
predictedRigidbody.interpolation = RigidbodyInterpolation.Interpolate;
|
predictedRigidbody.interpolation = RigidbodyInterpolation.Interpolate;
|
||||||
@ -91,6 +103,17 @@ protected virtual void Awake()
|
|||||||
positionCorrectionThresholdSqr = positionCorrectionThreshold * positionCorrectionThreshold;
|
positionCorrectionThresholdSqr = positionCorrectionThreshold * positionCorrectionThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// client prediction API
|
||||||
|
public void AddPredictedForce(Vector3 force, ForceMode mode)
|
||||||
|
{
|
||||||
|
// explicitly start predicting physics
|
||||||
|
predictedRigidbody.isKinematic = false;
|
||||||
|
predictedRigidbody.AddForce(force, mode);
|
||||||
|
state = ForecastState.PREDICT;
|
||||||
|
OnBeginPrediction();
|
||||||
|
Debug.Log($"{name} PREDICTING");
|
||||||
|
}
|
||||||
|
|
||||||
void UpdateServer()
|
void UpdateServer()
|
||||||
{
|
{
|
||||||
// bandwidth optimization while idle.
|
// bandwidth optimization while idle.
|
||||||
@ -127,37 +150,26 @@ protected virtual bool IsMoving() =>
|
|||||||
// when using Fast mode, we don't create any ghosts.
|
// when using Fast mode, we don't create any ghosts.
|
||||||
// but we still want to check IsMoving() in order to support the same
|
// but we still want to check IsMoving() in order to support the same
|
||||||
// user callbacks.
|
// user callbacks.
|
||||||
bool lastMoving = false;
|
void UpdateClient()
|
||||||
void UpdateState()
|
|
||||||
{
|
{
|
||||||
// perf: enough to check ghosts every few frames.
|
if (state == ForecastState.PREDICT)
|
||||||
// PredictionBenchmark: only checking every 4th frame: 770 => 800 FPS
|
|
||||||
if (Time.frameCount % checkGhostsEveryNthFrame != 0) return;
|
|
||||||
|
|
||||||
bool moving = IsMoving();
|
|
||||||
|
|
||||||
// started moving?
|
|
||||||
if (moving && !lastMoving)
|
|
||||||
{
|
{
|
||||||
OnBeginPrediction();
|
|
||||||
lastMoving = true;
|
|
||||||
}
|
}
|
||||||
// stopped moving?
|
else if (state == ForecastState.BLEND)
|
||||||
else if (!moving && lastMoving)
|
|
||||||
{
|
{
|
||||||
// ensure a minimum time since starting to move, to avoid on/off/on effects.
|
|
||||||
if (NetworkTime.time >= motionSmoothingLastMovedTime + motionSmoothingTimeTolerance)
|
}
|
||||||
{
|
else if (state == ForecastState.FOLLOW)
|
||||||
OnEndPrediction();
|
{
|
||||||
lastMoving = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update()
|
||||||
{
|
{
|
||||||
if (isServer) UpdateServer();
|
if (isServer) UpdateServer();
|
||||||
if (isClientOnly) UpdateState();
|
if (isClientOnly) UpdateClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FixedUpdate()
|
void FixedUpdate()
|
||||||
@ -468,8 +480,8 @@ void OnReceivedState(double timestamp, RigidbodyState state)//, bool sleeping)
|
|||||||
// blend the final correction towards current server state over time.
|
// blend the final correction towards current server state over time.
|
||||||
// this is the idea of ForecastRigidbody.
|
// this is the idea of ForecastRigidbody.
|
||||||
// TODO once we are at server state, let snapshot interpolation take over.
|
// TODO once we are at server state, let snapshot interpolation take over.
|
||||||
RigidbodyState blended = RigidbodyState.Interpolate(recomputed, state, blendPerSync);
|
// RigidbodyState blended = RigidbodyState.Interpolate(recomputed, state, blendPerSync);
|
||||||
Debug.DrawLine(recomputed.position, blended.position, Color.green, 10.0f);
|
// Debug.DrawLine(recomputed.position, blended.position, Color.green, 10.0f);
|
||||||
|
|
||||||
// log, draw & apply the final position.
|
// log, draw & apply the final position.
|
||||||
// always do this here, not when iterating above, in case we aren't iterating.
|
// always do this here, not when iterating above, in case we aren't iterating.
|
||||||
@ -477,7 +489,11 @@ void OnReceivedState(double timestamp, RigidbodyState state)//, bool sleeping)
|
|||||||
// int correctedAmount = stateHistory.Count - afterIndex;
|
// int correctedAmount = stateHistory.Count - afterIndex;
|
||||||
// Debug.Log($"Correcting {name}: {correctedAmount} / {stateHistory.Count} states to final position from: {rb.position} to: {last.position}");
|
// Debug.Log($"Correcting {name}: {correctedAmount} / {stateHistory.Count} states to final position from: {rb.position} to: {last.position}");
|
||||||
//Debug.DrawLine(physicsCopyRigidbody.position, recomputed.position, Color.green, lineTime);
|
//Debug.DrawLine(physicsCopyRigidbody.position, recomputed.position, Color.green, lineTime);
|
||||||
ApplyState(blended.timestamp, blended.position, blended.rotation, blended.velocity, blended.angularVelocity);
|
ApplyState(recomputed.timestamp, recomputed.position, recomputed.rotation, recomputed.velocity, recomputed.angularVelocity);
|
||||||
|
|
||||||
|
// insert the blended state into the history.
|
||||||
|
// this makes it permanent, instead of blending every time but rarely recording.
|
||||||
|
RecordState();
|
||||||
|
|
||||||
// user callback
|
// user callback
|
||||||
OnCorrected();
|
OnCorrected();
|
||||||
|
@ -165,6 +165,7 @@ MonoBehaviour:
|
|||||||
syncMode: 0
|
syncMode: 0
|
||||||
syncInterval: 0
|
syncInterval: 0
|
||||||
predictedRigidbody: {fileID: -177125271246800426}
|
predictedRigidbody: {fileID: -177125271246800426}
|
||||||
|
blendPerSync: 0.1
|
||||||
motionSmoothingVelocityThreshold: 0.1
|
motionSmoothingVelocityThreshold: 0.1
|
||||||
motionSmoothingAngularVelocityThreshold: 5
|
motionSmoothingAngularVelocityThreshold: 5
|
||||||
motionSmoothingTimeTolerance: 0.5
|
motionSmoothingTimeTolerance: 0.5
|
||||||
@ -177,7 +178,5 @@ MonoBehaviour:
|
|||||||
oneFrameAhead: 1
|
oneFrameAhead: 1
|
||||||
snapThreshold: 2
|
snapThreshold: 2
|
||||||
checkGhostsEveryNthFrame: 4
|
checkGhostsEveryNthFrame: 4
|
||||||
positionInterpolationSpeed: 15
|
|
||||||
rotationInterpolationSpeed: 10
|
|
||||||
teleportDistanceMultiplier: 10
|
teleportDistanceMultiplier: 10
|
||||||
reduceSendsWhileIdle: 1
|
reduceSendsWhileIdle: 1
|
||||||
|
@ -302,6 +302,7 @@ MonoBehaviour:
|
|||||||
syncMode: 0
|
syncMode: 0
|
||||||
syncInterval: 0
|
syncInterval: 0
|
||||||
predictedRigidbody: {fileID: 1848203816128897140}
|
predictedRigidbody: {fileID: 1848203816128897140}
|
||||||
|
blendPerSync: 0.1
|
||||||
motionSmoothingVelocityThreshold: 0.1
|
motionSmoothingVelocityThreshold: 0.1
|
||||||
motionSmoothingAngularVelocityThreshold: 5
|
motionSmoothingAngularVelocityThreshold: 5
|
||||||
motionSmoothingTimeTolerance: 0.5
|
motionSmoothingTimeTolerance: 0.5
|
||||||
@ -314,7 +315,5 @@ MonoBehaviour:
|
|||||||
oneFrameAhead: 1
|
oneFrameAhead: 1
|
||||||
snapThreshold: 2
|
snapThreshold: 2
|
||||||
checkGhostsEveryNthFrame: 4
|
checkGhostsEveryNthFrame: 4
|
||||||
positionInterpolationSpeed: 15
|
|
||||||
rotationInterpolationSpeed: 10
|
|
||||||
teleportDistanceMultiplier: 10
|
teleportDistanceMultiplier: 10
|
||||||
reduceSendsWhileIdle: 1
|
reduceSendsWhileIdle: 1
|
||||||
|
@ -36,7 +36,7 @@ void Awake()
|
|||||||
|
|
||||||
// apply force to white ball.
|
// apply force to white ball.
|
||||||
// common function to ensure we apply it the same way on server & client!
|
// common function to ensure we apply it the same way on server & client!
|
||||||
void ApplyForceToWhite(Vector3 force)
|
void ClientApplyForceToWhite(Vector3 force)
|
||||||
{
|
{
|
||||||
// https://docs.unity3d.com/2021.3/Documentation/ScriptReference/Rigidbody.AddForce.html
|
// https://docs.unity3d.com/2021.3/Documentation/ScriptReference/Rigidbody.AddForce.html
|
||||||
// this is buffered until the next FixedUpdate.
|
// this is buffered until the next FixedUpdate.
|
||||||
@ -44,26 +44,25 @@ void ApplyForceToWhite(Vector3 force)
|
|||||||
// get the white ball's Rigidbody.
|
// get the white ball's Rigidbody.
|
||||||
// prediction sometimes moves this out of the object for a while,
|
// prediction sometimes moves this out of the object for a while,
|
||||||
// so we need to grab it this way:
|
// so we need to grab it this way:
|
||||||
Rigidbody rb = whiteBall.GetComponent<ForecastRigidbody>().predictedRigidbody;
|
ForecastRigidbody forecast = whiteBall.GetComponent<ForecastRigidbody>();
|
||||||
|
|
||||||
// AddForce has different force modes, see this excellent diagram:
|
// AddForce has different force modes, see this excellent diagram:
|
||||||
// https://www.reddit.com/r/Unity3D/comments/psukm1/know_the_difference_between_forcemodes_a_little/
|
// https://www.reddit.com/r/Unity3D/comments/psukm1/know_the_difference_between_forcemodes_a_little/
|
||||||
// for prediction it's extremely important(!) to apply the correct mode:
|
// for prediction it's extremely important(!) to apply the correct mode:
|
||||||
// 'Force' makes server & client drift significantly here
|
// 'Force' makes server & client drift significantly here
|
||||||
// 'Impulse' is correct usage with significantly less drift
|
// 'Impulse' is correct usage with significantly less drift
|
||||||
rb.AddForce(force, ForceMode.Impulse);
|
forecast.AddPredictedForce(force, ForceMode.Impulse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// called when the local player dragged the white ball.
|
// called when the local player dragged the white ball.
|
||||||
// we reuse the white ball's OnMouseDrag and forward the event to here.
|
// we reuse the white ball's OnMouseDrag and forward the event to here.
|
||||||
public void OnDraggedBall(Vector3 force)
|
public void OnDraggedBall(Vector3 force)
|
||||||
{
|
{
|
||||||
// apply force locally immediately
|
// apply force on client (not in host mode)
|
||||||
ApplyForceToWhite(force);
|
if (!isServer) ClientApplyForceToWhite(force);
|
||||||
|
|
||||||
// apply on server as well.
|
// apply on server as well.
|
||||||
// not necessary in host mode, otherwise we would apply it twice.
|
CmdApplyForce(force);
|
||||||
if (!isServer) CmdApplyForce(force);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// while prediction is applied on clients immediately,
|
// while prediction is applied on clients immediately,
|
||||||
@ -85,7 +84,8 @@ void CmdApplyForce(Vector3 force)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// apply force
|
// apply force
|
||||||
ApplyForceToWhite(force);
|
Rigidbody rb = whiteBall.GetComponent<Rigidbody>();
|
||||||
|
rb.AddForce(force, ForceMode.Impulse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user