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
|
||||
{
|
||||
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))]
|
||||
public class ForecastRigidbody : NetworkBehaviour
|
||||
{
|
||||
@ -25,6 +32,7 @@ public class ForecastRigidbody : NetworkBehaviour
|
||||
|
||||
[Header("Blending")]
|
||||
[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.
|
||||
// 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.");
|
||||
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.
|
||||
// otherwise there's not going to be any smoothing whatsoever.
|
||||
predictedRigidbody.interpolation = RigidbodyInterpolation.Interpolate;
|
||||
@ -91,6 +103,17 @@ protected virtual void Awake()
|
||||
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()
|
||||
{
|
||||
// bandwidth optimization while idle.
|
||||
@ -127,37 +150,26 @@ protected virtual bool IsMoving() =>
|
||||
// when using Fast mode, we don't create any ghosts.
|
||||
// but we still want to check IsMoving() in order to support the same
|
||||
// user callbacks.
|
||||
bool lastMoving = false;
|
||||
void UpdateState()
|
||||
void UpdateClient()
|
||||
{
|
||||
// perf: enough to check ghosts every few frames.
|
||||
// PredictionBenchmark: only checking every 4th frame: 770 => 800 FPS
|
||||
if (Time.frameCount % checkGhostsEveryNthFrame != 0) return;
|
||||
|
||||
bool moving = IsMoving();
|
||||
|
||||
// started moving?
|
||||
if (moving && !lastMoving)
|
||||
if (state == ForecastState.PREDICT)
|
||||
{
|
||||
OnBeginPrediction();
|
||||
lastMoving = true;
|
||||
|
||||
}
|
||||
// stopped moving?
|
||||
else if (!moving && lastMoving)
|
||||
else if (state == ForecastState.BLEND)
|
||||
{
|
||||
// ensure a minimum time since starting to move, to avoid on/off/on effects.
|
||||
if (NetworkTime.time >= motionSmoothingLastMovedTime + motionSmoothingTimeTolerance)
|
||||
{
|
||||
OnEndPrediction();
|
||||
lastMoving = false;
|
||||
}
|
||||
|
||||
}
|
||||
else if (state == ForecastState.FOLLOW)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (isServer) UpdateServer();
|
||||
if (isClientOnly) UpdateState();
|
||||
if (isClientOnly) UpdateClient();
|
||||
}
|
||||
|
||||
void FixedUpdate()
|
||||
@ -468,8 +480,8 @@ void OnReceivedState(double timestamp, RigidbodyState state)//, bool sleeping)
|
||||
// blend the final correction towards current server state over time.
|
||||
// this is the idea of ForecastRigidbody.
|
||||
// TODO once we are at server state, let snapshot interpolation take over.
|
||||
RigidbodyState blended = RigidbodyState.Interpolate(recomputed, state, blendPerSync);
|
||||
Debug.DrawLine(recomputed.position, blended.position, Color.green, 10.0f);
|
||||
// RigidbodyState blended = RigidbodyState.Interpolate(recomputed, state, blendPerSync);
|
||||
// Debug.DrawLine(recomputed.position, blended.position, Color.green, 10.0f);
|
||||
|
||||
// log, draw & apply the final position.
|
||||
// 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;
|
||||
// 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);
|
||||
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
|
||||
OnCorrected();
|
||||
|
@ -165,6 +165,7 @@ MonoBehaviour:
|
||||
syncMode: 0
|
||||
syncInterval: 0
|
||||
predictedRigidbody: {fileID: -177125271246800426}
|
||||
blendPerSync: 0.1
|
||||
motionSmoothingVelocityThreshold: 0.1
|
||||
motionSmoothingAngularVelocityThreshold: 5
|
||||
motionSmoothingTimeTolerance: 0.5
|
||||
@ -177,7 +178,5 @@ MonoBehaviour:
|
||||
oneFrameAhead: 1
|
||||
snapThreshold: 2
|
||||
checkGhostsEveryNthFrame: 4
|
||||
positionInterpolationSpeed: 15
|
||||
rotationInterpolationSpeed: 10
|
||||
teleportDistanceMultiplier: 10
|
||||
reduceSendsWhileIdle: 1
|
||||
|
@ -302,6 +302,7 @@ MonoBehaviour:
|
||||
syncMode: 0
|
||||
syncInterval: 0
|
||||
predictedRigidbody: {fileID: 1848203816128897140}
|
||||
blendPerSync: 0.1
|
||||
motionSmoothingVelocityThreshold: 0.1
|
||||
motionSmoothingAngularVelocityThreshold: 5
|
||||
motionSmoothingTimeTolerance: 0.5
|
||||
@ -314,7 +315,5 @@ MonoBehaviour:
|
||||
oneFrameAhead: 1
|
||||
snapThreshold: 2
|
||||
checkGhostsEveryNthFrame: 4
|
||||
positionInterpolationSpeed: 15
|
||||
rotationInterpolationSpeed: 10
|
||||
teleportDistanceMultiplier: 10
|
||||
reduceSendsWhileIdle: 1
|
||||
|
@ -36,7 +36,7 @@ void Awake()
|
||||
|
||||
// apply force to white ball.
|
||||
// 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
|
||||
// this is buffered until the next FixedUpdate.
|
||||
@ -44,26 +44,25 @@ void ApplyForceToWhite(Vector3 force)
|
||||
// get the white ball's Rigidbody.
|
||||
// prediction sometimes moves this out of the object for a while,
|
||||
// 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:
|
||||
// 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:
|
||||
// 'Force' makes server & client drift significantly here
|
||||
// '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.
|
||||
// we reuse the white ball's OnMouseDrag and forward the event to here.
|
||||
public void OnDraggedBall(Vector3 force)
|
||||
{
|
||||
// apply force locally immediately
|
||||
ApplyForceToWhite(force);
|
||||
// apply force on client (not in host mode)
|
||||
if (!isServer) ClientApplyForceToWhite(force);
|
||||
|
||||
// apply on server as well.
|
||||
// not necessary in host mode, otherwise we would apply it twice.
|
||||
if (!isServer) CmdApplyForce(force);
|
||||
CmdApplyForce(force);
|
||||
}
|
||||
|
||||
// while prediction is applied on clients immediately,
|
||||
@ -85,7 +84,8 @@ void CmdApplyForce(Vector3 force)
|
||||
}
|
||||
|
||||
// apply force
|
||||
ApplyForceToWhite(force);
|
||||
Rigidbody rb = whiteBall.GetComponent<Rigidbody>();
|
||||
rb.AddForce(force, ForceMode.Impulse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user