Merge pull request #158 from Unity-Technologies/anticipation_sample_updates
Updated anticipation sample after API changes from review feedback in the anticipation branch.
This commit is contained in:
Коммит
c06fa7fe7b
|
@ -86,24 +86,27 @@ public class AnticipationSample : NetworkBehaviour
|
|||
|
||||
private const float k_ValueEChangePerSecond = 2.5f;
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
public override void OnReanticipate(double lastRoundTripTime)
|
||||
{
|
||||
// Initialize the reanticipation for all of the values:
|
||||
// C and D react to a request to reanticipate by simply smoothing between the previous anticipated value
|
||||
// and the new authoritative value. They are not frequently updated and only need any reanticipation action
|
||||
// when the anticipation was wrong.
|
||||
AnticipatedNetworkVariable<float>.OnReanticipateDelegate smooth = (AnticipatedNetworkVariable<float> variable, in float anticipatedValue, double anticipationTime, in float authoritativeValue, double authoritativeTime) =>
|
||||
|
||||
if (ValueC.ShouldReanticipate)
|
||||
{
|
||||
variable.Smooth(anticipatedValue, authoritativeValue, SmoothTime, Mathf.Lerp);
|
||||
};
|
||||
ValueC.OnReanticipate = smooth;
|
||||
ValueD.OnReanticipate = smooth;
|
||||
ValueC.Smooth(ValueC.PreviousAnticipatedValue, ValueC.AuthoritativeValue, SmoothTime, Mathf.Lerp);
|
||||
}
|
||||
if (ValueD.ShouldReanticipate)
|
||||
{
|
||||
ValueD.Smooth(ValueD.PreviousAnticipatedValue, ValueD.AuthoritativeValue, SmoothTime, Mathf.Lerp);
|
||||
}
|
||||
|
||||
// E is actually trying to anticipate the current value of a constantly changing object to hide latency.
|
||||
// It uses the amount of time that has passed since the authoritativetime to gauge the latency of this update
|
||||
// and anticipates a new value based on that delay. The server value is in the past, so the predicted value
|
||||
// attempts to guess what the value is in the present.
|
||||
ValueE.OnReanticipate = (AnticipatedNetworkVariable<float> variable, in float anticipatedValue, double anticipationTime, in float authoritativeValue, double authoritativeTime) =>
|
||||
if(ValueE.ShouldReanticipate)
|
||||
{
|
||||
// There is an important distinction between the smoothing this is doing and the smoothing the player object
|
||||
// is doing:
|
||||
|
@ -113,15 +116,15 @@ public class AnticipationSample : NetworkBehaviour
|
|||
// value - the difference between current time and authoritativeTime represents a full round trip, but the
|
||||
// actual time difference here is only a half round trip, so we multiply by 0.5.
|
||||
// Then, because smoothing adds its own latency, we add the smooth time into the mix.
|
||||
var secondsBehind = (NetworkManager.LocalTime.Time - authoritativeTime) * 0.5f + SmoothTime;
|
||||
var secondsBehind = lastRoundTripTime * 0.5f + SmoothTime;
|
||||
|
||||
var newAnticipatedValue = (float)(authoritativeValue + k_ValueEChangePerSecond * secondsBehind) % 10;
|
||||
var newAnticipatedValue = (float)(ValueE.AuthoritativeValue + k_ValueEChangePerSecond * secondsBehind) % 10;
|
||||
|
||||
// This variable uses a custom interpolation callback that handles the drop from 10
|
||||
// down to 0. Without this, there is either weird smoothing behavior, or hitching.
|
||||
// This keeps the interpolation going, and handles the case where the interpolated value
|
||||
// goes over 10 and has to jump back to 0.
|
||||
variable.Smooth(anticipatedValue, newAnticipatedValue, SmoothTime, ((start, end, amount) =>
|
||||
ValueE.Smooth(ValueE.PreviousAnticipatedValue, newAnticipatedValue, SmoothTime, ((start, end, amount) =>
|
||||
{
|
||||
if (end < 3 && start > 7)
|
||||
{
|
||||
|
|
|
@ -53,6 +53,14 @@ namespace DefaultNamespace
|
|||
m_History.RemoveAll(item => item.Time > time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all items from the history
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
m_History.Clear();;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the full history, useful for iterating through all the values to reapply them when reanticipating.
|
||||
/// </summary>
|
||||
|
|
|
@ -102,5 +102,13 @@ namespace DefaultNamespace
|
|||
{
|
||||
return m_HistoricalInput.GetHistory();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove all items from the history
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
m_HistoricalInput.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,46 +106,66 @@ namespace DefaultNamespace
|
|||
}
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
public override void OnReanticipate(double lastRoundTripTime)
|
||||
{
|
||||
MyTransform.OnReanticipate = (networkTransform, anticipatedValue, anticipationTime, authorityValue, authorityTime) =>
|
||||
{
|
||||
// Here we re-anticipate the new position of the player based on the updated server position.
|
||||
// We do this by taking the current authoritative position and replaying every input we have received
|
||||
// since the reported authority time, re-applying all the movement we have applied since then
|
||||
// to arrive at a new anticipated player location.
|
||||
foreach (var item in InputManager.GetHistory())
|
||||
{
|
||||
if (item.Time <= authorityTime)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Have to store the transform's previous state because calls to AnticipateMove() and
|
||||
// AnticipateRotate() will overwrite it.
|
||||
var previousState = MyTransform.PreviousAnticipatedState;
|
||||
|
||||
Move(item.Item, true);
|
||||
var authorityTime = NetworkManager.LocalTime.Time - lastRoundTripTime;
|
||||
// Here we re-anticipate the new position of the player based on the updated server position.
|
||||
// We do this by taking the current authoritative position and replaying every input we have received
|
||||
// since the reported authority time, re-applying all the movement we have applied since then
|
||||
// to arrive at a new anticipated player location.
|
||||
|
||||
foreach (var item in InputManager.GetHistory())
|
||||
{
|
||||
if (item.Time <= authorityTime)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Clear out all the input history before the given authority time. We don't need anything before that
|
||||
// anymore as we won't get any more updates from the server from before this one. We keep the current
|
||||
// authority time because theoretically another system may need that.
|
||||
InputManager.RemoveBefore(authorityTime);
|
||||
// It's not always desirable to smooth the transform. In cases of very large discrepencies in state,
|
||||
// it can sometimes be desirable to simply teleport to the new position. We use the SmoothDistance
|
||||
// value (and use SqrMagnitude instead of Distance for efficiency) as a threshold for teleportation.
|
||||
// This could also use other mechanisms of detection: For example, when the Telport input is included
|
||||
// in the replay set, we could set a flag to disable smoothing because we know we are teleporting.
|
||||
if (SmoothTime != 0.0 && Vector3.SqrMagnitude(anticipatedValue.Position - networkTransform.AnticipatedState.Position) < SmoothDistance * SmoothDistance)
|
||||
|
||||
Move(item.Item, true);
|
||||
}
|
||||
// Clear out all the input history before the given authority time. We don't need anything before that
|
||||
// anymore as we won't get any more updates from the server from before this one. We keep the current
|
||||
// authority time because theoretically another system may need that.
|
||||
InputManager.RemoveBefore(authorityTime);
|
||||
// It's not always desirable to smooth the transform. In cases of very large discrepencies in state,
|
||||
// it can sometimes be desirable to simply teleport to the new position. We use the SmoothDistance
|
||||
// value (and use SqrMagnitude instead of Distance for efficiency) as a threshold for teleportation.
|
||||
// This could also use other mechanisms of detection: For example, when the Telport input is included
|
||||
// in the replay set, we could set a flag to disable smoothing because we know we are teleporting.
|
||||
if (SmoothTime != 0.0)
|
||||
{
|
||||
var sqDist = Vector3.SqrMagnitude(previousState.Position - MyTransform.AnticipatedState.Position);
|
||||
if (sqDist <= 0.25 * 0.25)
|
||||
{
|
||||
// This prevents small amounts of wobble from slight differences.
|
||||
MyTransform.AnticipateState(previousState);
|
||||
}
|
||||
else if (sqDist < SmoothDistance * SmoothDistance)
|
||||
{
|
||||
// Server updates are not necessarily smooth, so applying reanticipation can also result in
|
||||
// hitchy, unsmooth animations. To compensate for that, we call this to smooth from the previous
|
||||
// anticipated state (stored in "anticipatedValue") to the new state (which, because we have used
|
||||
// the "Move" method that updates the anticipated state of the transform, is now the current
|
||||
// transform anticipated state)
|
||||
networkTransform.Smooth(anticipatedValue, networkTransform.AnticipatedState, SmoothTime);
|
||||
MyTransform.Smooth(previousState, MyTransform.AnticipatedState, SmoothTime);
|
||||
}
|
||||
};
|
||||
base.OnNetworkSpawn();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When we apply changes to the latency and jitter, it respawns everything.
|
||||
/// We want to make sure there's no input left over from before that by clearing it.
|
||||
/// </summary>
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
InputManager.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pass client inputs to the server so the server can mirror the client simulation.
|
||||
///
|
||||
|
@ -162,16 +182,16 @@ namespace DefaultNamespace
|
|||
// just reuse the same method here with no problem.
|
||||
Move(inputs);
|
||||
// Server can use Smoothing for interpolation purposes as well.
|
||||
MyTransform.Smooth(currentPosition, MyTransform.AuthorityState, SmoothTime);
|
||||
MyTransform.Smooth(currentPosition, MyTransform.AuthoritativeState, SmoothTime);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
// The "ghost transform" here is a little smaller player object that shows the current authority position,
|
||||
// which is a few frames behind our anticipated value. This helps render the difference.
|
||||
GhostTrasform.position = MyTransform.AuthorityState.Position;
|
||||
GhostTrasform.rotation = MyTransform.AuthorityState.Rotation;
|
||||
GhostTrasform.localScale = MyTransform.AuthorityState.Scale * 0.75f;
|
||||
GhostTrasform.position = MyTransform.AuthoritativeState.Position;
|
||||
GhostTrasform.rotation = MyTransform.AuthoritativeState.Rotation;
|
||||
GhostTrasform.localScale = MyTransform.AuthoritativeState.Scale * 0.75f;
|
||||
}
|
||||
|
||||
// Input processing happens in FixedUpdate rather than Update because the frame rate of server and client
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
"com.unity.nuget.mono-cecil": "1.10.1",
|
||||
"com.unity.transport": "1.4.0"
|
||||
},
|
||||
"hash": "6eec9634c17a2d16fb18dbca56c0a3280ff2c39e"
|
||||
"hash": "227a33222f0b796c37fbdcab2641592f5310301b"
|
||||
},
|
||||
"com.unity.nuget.mono-cecil": {
|
||||
"version": "1.10.1",
|
||||
|
|
Загрузка…
Ссылка в новой задаче