Skip to content

Commit c06fa7f

Browse files
authored
Merge pull request #158 from Unity-Technologies/anticipation_sample_updates
Updated anticipation sample after API changes from review feedback in the anticipation branch.
2 parents 56154f1 + 4f98348 commit c06fa7f

File tree

5 files changed

+79
-40
lines changed

5 files changed

+79
-40
lines changed

Experimental/Anticipation Sample/Assets/Scripts/AnticipationSample.cs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,24 +86,27 @@ void LogEverywhereRpc(FixedString128Bytes message)
8686

8787
private const float k_ValueEChangePerSecond = 2.5f;
8888

89-
public override void OnNetworkSpawn()
89+
public override void OnReanticipate(double lastRoundTripTime)
9090
{
9191
// Initialize the reanticipation for all of the values:
9292
// C and D react to a request to reanticipate by simply smoothing between the previous anticipated value
9393
// and the new authoritative value. They are not frequently updated and only need any reanticipation action
9494
// when the anticipation was wrong.
95-
AnticipatedNetworkVariable<float>.OnReanticipateDelegate smooth = (AnticipatedNetworkVariable<float> variable, in float anticipatedValue, double anticipationTime, in float authoritativeValue, double authoritativeTime) =>
95+
96+
if (ValueC.ShouldReanticipate)
9697
{
97-
variable.Smooth(anticipatedValue, authoritativeValue, SmoothTime, Mathf.Lerp);
98-
};
99-
ValueC.OnReanticipate = smooth;
100-
ValueD.OnReanticipate = smooth;
98+
ValueC.Smooth(ValueC.PreviousAnticipatedValue, ValueC.AuthoritativeValue, SmoothTime, Mathf.Lerp);
99+
}
100+
if (ValueD.ShouldReanticipate)
101+
{
102+
ValueD.Smooth(ValueD.PreviousAnticipatedValue, ValueD.AuthoritativeValue, SmoothTime, Mathf.Lerp);
103+
}
101104

102105
// E is actually trying to anticipate the current value of a constantly changing object to hide latency.
103106
// It uses the amount of time that has passed since the authoritativetime to gauge the latency of this update
104107
// and anticipates a new value based on that delay. The server value is in the past, so the predicted value
105108
// attempts to guess what the value is in the present.
106-
ValueE.OnReanticipate = (AnticipatedNetworkVariable<float> variable, in float anticipatedValue, double anticipationTime, in float authoritativeValue, double authoritativeTime) =>
109+
if(ValueE.ShouldReanticipate)
107110
{
108111
// There is an important distinction between the smoothing this is doing and the smoothing the player object
109112
// is doing:
@@ -113,15 +116,15 @@ public override void OnNetworkSpawn()
113116
// value - the difference between current time and authoritativeTime represents a full round trip, but the
114117
// actual time difference here is only a half round trip, so we multiply by 0.5.
115118
// Then, because smoothing adds its own latency, we add the smooth time into the mix.
116-
var secondsBehind = (NetworkManager.LocalTime.Time - authoritativeTime) * 0.5f + SmoothTime;
119+
var secondsBehind = lastRoundTripTime * 0.5f + SmoothTime;
117120

118-
var newAnticipatedValue = (float)(authoritativeValue + k_ValueEChangePerSecond * secondsBehind) % 10;
121+
var newAnticipatedValue = (float)(ValueE.AuthoritativeValue + k_ValueEChangePerSecond * secondsBehind) % 10;
119122

120123
// This variable uses a custom interpolation callback that handles the drop from 10
121124
// down to 0. Without this, there is either weird smoothing behavior, or hitching.
122125
// This keeps the interpolation going, and handles the case where the interpolated value
123126
// goes over 10 and has to jump back to 0.
124-
variable.Smooth(anticipatedValue, newAnticipatedValue, SmoothTime, ((start, end, amount) =>
127+
ValueE.Smooth(ValueE.PreviousAnticipatedValue, newAnticipatedValue, SmoothTime, ((start, end, amount) =>
125128
{
126129
if (end < 3 && start > 7)
127130
{

Experimental/Anticipation Sample/Assets/Scripts/FrameHistory.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ public void RemoveAfter(double time)
5353
m_History.RemoveAll(item => item.Time > time);
5454
}
5555

56+
/// <summary>
57+
/// Remove all items from the history
58+
/// </summary>
59+
public void Clear()
60+
{
61+
m_History.Clear();;
62+
}
63+
5664
/// <summary>
5765
/// Get the full history, useful for iterating through all the values to reapply them when reanticipating.
5866
/// </summary>

Experimental/Anticipation Sample/Assets/Scripts/InputManager.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,13 @@ public void RemoveBefore(double time)
102102
{
103103
return m_HistoricalInput.GetHistory();
104104
}
105+
106+
/// <summary>
107+
/// Remove all items from the history
108+
/// </summary>
109+
public void Clear()
110+
{
111+
m_HistoricalInput.Clear();
112+
}
105113
}
106114
}

Experimental/Anticipation Sample/Assets/Scripts/PlayerMovableObject.cs

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -106,44 +106,64 @@ public void Move(InputList inputs, bool replay = false)
106106
}
107107
}
108108

109-
public override void OnNetworkSpawn()
109+
public override void OnReanticipate(double lastRoundTripTime)
110110
{
111-
MyTransform.OnReanticipate = (networkTransform, anticipatedValue, anticipationTime, authorityValue, authorityTime) =>
111+
// Have to store the transform's previous state because calls to AnticipateMove() and
112+
// AnticipateRotate() will overwrite it.
113+
var previousState = MyTransform.PreviousAnticipatedState;
114+
115+
var authorityTime = NetworkManager.LocalTime.Time - lastRoundTripTime;
116+
// Here we re-anticipate the new position of the player based on the updated server position.
117+
// We do this by taking the current authoritative position and replaying every input we have received
118+
// since the reported authority time, re-applying all the movement we have applied since then
119+
// to arrive at a new anticipated player location.
120+
121+
foreach (var item in InputManager.GetHistory())
112122
{
113-
// Here we re-anticipate the new position of the player based on the updated server position.
114-
// We do this by taking the current authoritative position and replaying every input we have received
115-
// since the reported authority time, re-applying all the movement we have applied since then
116-
// to arrive at a new anticipated player location.
117-
foreach (var item in InputManager.GetHistory())
123+
if (item.Time <= authorityTime)
118124
{
119-
if (item.Time <= authorityTime)
120-
{
121-
continue;
122-
}
125+
continue;
126+
}
123127

124-
Move(item.Item, true);
128+
Move(item.Item, true);
129+
}
130+
// Clear out all the input history before the given authority time. We don't need anything before that
131+
// anymore as we won't get any more updates from the server from before this one. We keep the current
132+
// authority time because theoretically another system may need that.
133+
InputManager.RemoveBefore(authorityTime);
134+
// It's not always desirable to smooth the transform. In cases of very large discrepencies in state,
135+
// it can sometimes be desirable to simply teleport to the new position. We use the SmoothDistance
136+
// value (and use SqrMagnitude instead of Distance for efficiency) as a threshold for teleportation.
137+
// This could also use other mechanisms of detection: For example, when the Telport input is included
138+
// in the replay set, we could set a flag to disable smoothing because we know we are teleporting.
139+
if (SmoothTime != 0.0)
140+
{
141+
var sqDist = Vector3.SqrMagnitude(previousState.Position - MyTransform.AnticipatedState.Position);
142+
if (sqDist <= 0.25 * 0.25)
143+
{
144+
// This prevents small amounts of wobble from slight differences.
145+
MyTransform.AnticipateState(previousState);
125146
}
126-
// Clear out all the input history before the given authority time. We don't need anything before that
127-
// anymore as we won't get any more updates from the server from before this one. We keep the current
128-
// authority time because theoretically another system may need that.
129-
InputManager.RemoveBefore(authorityTime);
130-
// It's not always desirable to smooth the transform. In cases of very large discrepencies in state,
131-
// it can sometimes be desirable to simply teleport to the new position. We use the SmoothDistance
132-
// value (and use SqrMagnitude instead of Distance for efficiency) as a threshold for teleportation.
133-
// This could also use other mechanisms of detection: For example, when the Telport input is included
134-
// in the replay set, we could set a flag to disable smoothing because we know we are teleporting.
135-
if (SmoothTime != 0.0 && Vector3.SqrMagnitude(anticipatedValue.Position - networkTransform.AnticipatedState.Position) < SmoothDistance * SmoothDistance)
147+
else if (sqDist < SmoothDistance * SmoothDistance)
136148
{
137149
// Server updates are not necessarily smooth, so applying reanticipation can also result in
138150
// hitchy, unsmooth animations. To compensate for that, we call this to smooth from the previous
139151
// anticipated state (stored in "anticipatedValue") to the new state (which, because we have used
140152
// the "Move" method that updates the anticipated state of the transform, is now the current
141153
// transform anticipated state)
142-
networkTransform.Smooth(anticipatedValue, networkTransform.AnticipatedState, SmoothTime);
154+
MyTransform.Smooth(previousState, MyTransform.AnticipatedState, SmoothTime);
143155
}
144-
};
145-
base.OnNetworkSpawn();
156+
}
157+
158+
}
146159

160+
/// <summary>
161+
/// When we apply changes to the latency and jitter, it respawns everything.
162+
/// We want to make sure there's no input left over from before that by clearing it.
163+
/// </summary>
164+
public override void OnNetworkSpawn()
165+
{
166+
InputManager.Clear();
147167
}
148168

149169
/// <summary>
@@ -162,16 +182,16 @@ private void ServerMoveRpc(InputList inputs)
162182
// just reuse the same method here with no problem.
163183
Move(inputs);
164184
// Server can use Smoothing for interpolation purposes as well.
165-
MyTransform.Smooth(currentPosition, MyTransform.AuthorityState, SmoothTime);
185+
MyTransform.Smooth(currentPosition, MyTransform.AuthoritativeState, SmoothTime);
166186
}
167187

168188
public void Update()
169189
{
170190
// The "ghost transform" here is a little smaller player object that shows the current authority position,
171191
// which is a few frames behind our anticipated value. This helps render the difference.
172-
GhostTrasform.position = MyTransform.AuthorityState.Position;
173-
GhostTrasform.rotation = MyTransform.AuthorityState.Rotation;
174-
GhostTrasform.localScale = MyTransform.AuthorityState.Scale * 0.75f;
192+
GhostTrasform.position = MyTransform.AuthoritativeState.Position;
193+
GhostTrasform.rotation = MyTransform.AuthoritativeState.Rotation;
194+
GhostTrasform.localScale = MyTransform.AuthoritativeState.Scale * 0.75f;
175195
}
176196

177197
// Input processing happens in FixedUpdate rather than Update because the frame rate of server and client

Experimental/Anticipation Sample/Packages/packages-lock.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
"com.unity.nuget.mono-cecil": "1.10.1",
9595
"com.unity.transport": "1.4.0"
9696
},
97-
"hash": "6eec9634c17a2d16fb18dbca56c0a3280ff2c39e"
97+
"hash": "227a33222f0b796c37fbdcab2641592f5310301b"
9898
},
9999
"com.unity.nuget.mono-cecil": {
100100
"version": "1.10.1",

0 commit comments

Comments
 (0)