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:
Kitty Draper 2024-05-03 16:21:56 -05:00 коммит произвёл GitHub
Родитель 56154f106d 4f98348bb6
Коммит c06fa7fe7b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
5 изменённых файлов: 81 добавлений и 42 удалений

Просмотреть файл

@ -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",