-
Notifications
You must be signed in to change notification settings - Fork 2
/
NetworkEnemyPositionInterpolationController.cs
383 lines (291 loc) · 13.6 KB
/
NetworkEnemyPositionInterpolationController.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
using System.Collections;
using System.Collections.Generic;
using BeardedManStudios.Forge.Networking;
using BeardedManStudios.Forge.Networking.Unity;
using UnityEngine;
using NaughtyAttributes;
///See NetworkPositionInterpolationController
//Same logic, but instead of just 1 (Warrior), it is for all Monsters.
//Hence, it is the same as NetworkPositionInterpolationController but individual logic now has a forloop list wrapper ;)
public class NetworkEnemyPositionInterpolationController : MonoBehaviour
{
public static NetworkEnemyPositionInterpolationController globalInstance;
[ReadOnly]
public bool activated = false;
public List<ushort> activatedEnemyListIndex = new List<ushort>();
private NetworkEnemyAnimationOverrideController commonNetworkEnemyAnimationOverrideController;
[Header("Position Snapshot")]
[Tooltip("0.02 seconds is 50 milliseconds")]
public double timePerSnapshot = 0.020d;
//[Tooltip("[0] is current, [1] is the one we interpolate towards, [2] and [3] are the 2 next ones, and so on")]
public Dictionary<ushort, List<PositionSnapshot>> snapshotDictionary = new Dictionary<ushort, List<PositionSnapshot>>();
public Dictionary<ushort, Rigidbody2D> activatedRigidbodyDictionary = new Dictionary<ushort, Rigidbody2D>();
private Dictionary<ushort, double> timePassedSinceLastPosition = new Dictionary<ushort, double>();
private Dictionary<ushort, double> timePassedSinceLastPositionTotal = new Dictionary<ushort, double>();
private Dictionary<ushort, float[]> duplicateSnapshotDictionary = new Dictionary<ushort, float[]>();
public Vector2 lerpResultPosition;
public double lerpRate;
//Hacky flag to trigger ClientRestartEnemies() on the first time they connect
private bool clientInitialMonstersSynced = false;
private int latestDebugPrintCount = -1;
private int currentDebugPrintCount;
// Use this for initialization
void Awake()
{
//Setting our singleton
globalInstance = this;
commonNetworkEnemyAnimationOverrideController = GetComponent<NetworkEnemyAnimationOverrideController>();
commonNetworkEnemyAnimationOverrideController.activatedEnemyListIndex = activatedEnemyListIndex;
}
public void Activate()
{
if (activated == false)
activated = true;
else
return;
NetworkCommunicationController.globalInstance.ReceiveMonsterPositionEvent += UpdateLatestMonsterPosition;
NetworkCommunicationController.globalInstance.ReceiveClearSynchronizedMonstersEvent += ReceivedClearEnemyIndexes;
NetworkCommunicationController.globalInstance.ReceiveAddedSynchronizedMonstersEvent += AddEnemyIndexListFromClient;
//Activated enemies register here, not the reverse.
//if (warriorBehaviour != null)
//warriorBehaviour.isGrounded += TouchedGround;
}
public void DeActivate()
{
activated = false;
NetworkCommunicationController.globalInstance.ReceiveMonsterPositionEvent -= UpdateLatestMonsterPosition;
NetworkCommunicationController.globalInstance.ReceiveClearSynchronizedMonstersEvent -= ReceivedClearEnemyIndexes;
NetworkCommunicationController.globalInstance.ReceiveAddedSynchronizedMonstersEvent -= AddEnemyIndexListFromClient;
//Deactivated enemies register here, not the reverse.
//if (warriorBehaviour != null)
//warriorBehaviour.isGrounded -= TouchedGround;
}
public void UpdateLatestMonsterPosition(RpcArgs args)
{
double receivedSnapshotTime = args.GetNext<double>();
Vector2 receivedMonsterPosition = args.GetNext<Vector2>();
ushort receivedMonsterIndex = args.GetNext<ushort>();
//If host sent monster snapshot, before the client even has registered it, register it via client
if (snapshotDictionary.ContainsKey(receivedMonsterIndex) == false)
AddEnemyIndex(receivedMonsterIndex, LevelManager.globalInstance.GetEnemyRigidbodyFromIndex(receivedMonsterIndex));
snapshotDictionary[receivedMonsterIndex].Add(new PositionSnapshot { timestamp = receivedSnapshotTime, warriorPosition = receivedMonsterPosition});
//If 2nd snapshot, put the timestamp to start properly!
if (snapshotDictionary[receivedMonsterIndex].Count == 2)
timePassedSinceLastPosition[receivedMonsterIndex] = snapshotDictionary[receivedMonsterIndex][0].timestamp;
}
public void Update()
{
if (activated == false)
return;
//If disconnected but somehow still not updated (heavy bug with packets)
if (NetworkCommunicationController.globalInstance == null)
{
Debug.LogError("Weird bizzaro bug where disconnection did not happen smoothly. NetworkCommunicationController is null.");
return;
}
DebugPrintSnapshotList();
if (activatedEnemyListIndex.Count > 0 && NetworkDamageShare.globalInstance.IsSynchronized())
ProcessMonsterPositions();
}
public void DebugPrintSnapshotList()
{
currentDebugPrintCount = snapshotDictionary.Count;
if (currentDebugPrintCount != latestDebugPrintCount)
{
Debug.Log("Enemies Registered: " + currentDebugPrintCount);
int i = 0;
foreach(KeyValuePair<ushort, Rigidbody2D> entry in activatedRigidbodyDictionary)
{
if (entry.Value != null)
Debug.Log("Enemy" + i + " Collider's Name: " + entry.Value.gameObject.name);
i++;
}
}
latestDebugPrintCount = currentDebugPrintCount;
}
//Called each frame/Update()!
public void ProcessMonsterPositions()
{
//=======================================
//===If host -> Send Position ===
//===If client -> Interpolate Position===
//=======================================
//If host -> Send position
if (NetworkCommunicationController.globalInstance.IsServer())
{
//Debug.Log("Time.unscaledDeltaTime: " + Time.unscaledDeltaTime);
//Iterate for each monster (i is picked enemy index)
ushort i;
for (int j = 0; j < activatedEnemyListIndex.Count; j++)
{
i = activatedEnemyListIndex[j];
if (snapshotDictionary.ContainsKey(i) == false)
{
Debug.LogError("Host has Key " + i + " which doesn't exist!");
//RemoveEnemyIndex(i);
//i--;
continue;
}
//Update the timestamps below, and send by timer.
timePassedSinceLastPosition[i] = timePassedSinceLastPosition[i] + Time.unscaledDeltaTime;
timePassedSinceLastPositionTotal[i] = timePassedSinceLastPositionTotal[i] + Time.unscaledDeltaTime;
if (timePassedSinceLastPosition[i] >= timePerSnapshot || timePassedSinceLastPositionTotal[i] == 0d)
{
timePassedSinceLastPosition[i] = 0;
//Debug.Log(duplicateSnapshotDictionary[i]);
//Get current snapshot, and check if its different than last one
//The goal of this is to not send idle-movement snapshots and overflow the bandwidth
//Get current position
duplicateSnapshotDictionary[i][0] = activatedRigidbodyDictionary[i].transform.position.ToVector2().x;
duplicateSnapshotDictionary[i][1] = activatedRigidbodyDictionary[i].transform.position.ToVector2().y;
//If current position is different than previous position, send the snapshot
if (duplicateSnapshotDictionary[i][0] != duplicateSnapshotDictionary[i][2] || duplicateSnapshotDictionary[i][1] != duplicateSnapshotDictionary[i][3])
{
NetworkCommunicationController.globalInstance.SendMonsterPositionUnreliable(activatedRigidbodyDictionary[i].transform.position.ToVector2(), timePassedSinceLastPositionTotal[i], i);
//activatedRigidbodyDictionary[i].transform.position.ToVector2();
Debug.Log("SENDING"+i);
//Cache the "previous position"
duplicateSnapshotDictionary[i][2] = duplicateSnapshotDictionary[i][0];
duplicateSnapshotDictionary[i][3] = duplicateSnapshotDictionary[i][1];
}
}
}
}
else//InterpolateMonsterToFinalMonsterPosition
{
//Iterate for each monster (i is picked enemy index)
ushort i;
for (int j = 0; j < activatedEnemyListIndex.Count; j++)
{
i = activatedEnemyListIndex[j];
if (snapshotDictionary.ContainsKey(i) == false)
continue;
//Not enough snapshots to do any interpolation, just gtfo
if (snapshotDictionary[i].Count < 2)
continue;
while (snapshotDictionary[i].Count > 2 && timePassedSinceLastPosition[i] > snapshotDictionary[i][1].timestamp)
{
snapshotDictionary[i].RemoveAt(0);
}
//Debug.Log("Monster's Snapshot Count: " +snapshotDictionary[i].Count);
//Debug.Log("Monster Snapshot: " + snapshotDictionary[i][snapshotDictionary[i].Count - 1].timestamp + " X " + snapshotDictionary[i][snapshotDictionary[i].Count - 1].warriorPosition.x);
lerpRate = (timePassedSinceLastPosition[i] - snapshotDictionary[i][0].timestamp) / (snapshotDictionary[i][1].timestamp - snapshotDictionary[i][0].timestamp);
lerpResultPosition = Vector2.Lerp(snapshotDictionary[i][0].warriorPosition, snapshotDictionary[i][1].warriorPosition, (float)lerpRate);
//This is for a VERY rare bug (only on high pings) where lerpResultPosition is NaN...
if (float.IsNaN(lerpResultPosition.x))
continue;
//Having calculated the interpolated position, place it onto the monster (assuming it exists ofc and isnt removed)
if (activatedRigidbodyDictionary[i] != null)
{
activatedRigidbodyDictionary[i].transform.position = new Vector3(lerpResultPosition.x, lerpResultPosition.y, 0);
timePassedSinceLastPosition[i] = timePassedSinceLastPosition[i] + Time.unscaledDeltaTime;
}
else
RemoveEnemyIndex(i);//If it still errors, just comment this out and let it be detected+removed elsewhere
}
}
}
public void AddEnemyIndex(ushort monsterIndexToAdd, Rigidbody2D monsterRigidbodyToAdd)
{
//Debug.Log("Enemy Index ADDED " + monsterIndexToAdd);
if (snapshotDictionary.ContainsKey(monsterIndexToAdd))
{
Debug.LogWarning("Already added");
return;
}
activatedEnemyListIndex.Add(monsterIndexToAdd);
activatedRigidbodyDictionary.Add(monsterIndexToAdd, monsterRigidbodyToAdd);
snapshotDictionary.Add(monsterIndexToAdd, new List<PositionSnapshot>());
timePassedSinceLastPosition.Add(monsterIndexToAdd, 0d);
timePassedSinceLastPositionTotal.Add(monsterIndexToAdd, 0d);
duplicateSnapshotDictionary.Add(monsterIndexToAdd, new float[4]{-1, -1, -1, -1});
duplicateSnapshotDictionary[monsterIndexToAdd][2] = activatedRigidbodyDictionary[monsterIndexToAdd].transform.position.ToVector2().x;
duplicateSnapshotDictionary[monsterIndexToAdd][3] = activatedRigidbodyDictionary[monsterIndexToAdd].transform.position.ToVector2().y;
commonNetworkEnemyAnimationOverrideController.AddAnimator(monsterIndexToAdd);
//Normally, should return short e.g. -1 and then remove if -1 instead of nothing/end
}
public void RemoveEnemyIndex(ushort monsterIndexToRemove)
{
if (snapshotDictionary.ContainsKey(monsterIndexToRemove))
{
snapshotDictionary.Remove(monsterIndexToRemove);
activatedRigidbodyDictionary.Remove(monsterIndexToRemove);
activatedEnemyListIndex.Remove(monsterIndexToRemove);
timePassedSinceLastPosition.Remove(monsterIndexToRemove);
timePassedSinceLastPositionTotal.Remove(monsterIndexToRemove);
duplicateSnapshotDictionary.Remove(monsterIndexToRemove);
commonNetworkEnemyAnimationOverrideController.RemoveAnimator(monsterIndexToRemove);
}
else
Debug.LogWarning("Tried to remove a monster index which is already removed: " + monsterIndexToRemove);
}
public void ClearAllEnemyIndexes()
{
while (activatedEnemyListIndex.Count > 0)
RemoveEnemyIndex(activatedEnemyListIndex[activatedEnemyListIndex.Count - 1]);
}
public void ReceivedClearEnemyIndexes(RpcArgs args)
{
ClearAllEnemyIndexes();
}
public void SendEntireEnemyIndexList()
{
if (activatedEnemyListIndex.Count < 1)
return;//Don't send empty list across the network!
NetworkCommunicationController.globalInstance.SendAddedSynchronizedMonsters(activatedEnemyListIndex);
}
public void AddEnemyIndexListFromClient(RpcArgs args)
{
// This line below should be in pretty much every RPC that takes custom-data types.
byte[] receivedBytes = args.GetNext<byte[]>();
List<ushort> tempList = receivedBytes.ByteArrayToObject<List<ushort>>();
bool replaceList = true;
//If lists are the same, don't empty and refill
if (tempList.Count == activatedEnemyListIndex.Count)
{
replaceList = false;
for (int i = 0; i < tempList.Count; i++)
{
if (tempList[i] != activatedEnemyListIndex[i])
{
replaceList = true;
break;
}
}
if (replaceList == false)
return;
}
Debug.Log("Adding EnemyIndexList from Client, Count: " + tempList.Count);
//Clear current list
ClearAllEnemyIndexes();
//Create a brand new list
for (int i = 0; i < tempList.Count; i++)
AddEnemyIndex(tempList[i], LevelManager.globalInstance.GetEnemyRigidbodyFromIndex(tempList[i]));
if (NetworkCommunicationController.globalInstance.IsServer() == false && clientInitialMonstersSynced == false)
{
clientInitialMonstersSynced = true;
ClientRestartEnemies();
NetworkCommunicationController.globalInstance.networkObject.ClearRpcBuffer();
}
}
//When desync happens, checks if coroutineRunning == false, and restarts coroutine properly
public void ClientRestartEnemies()
{
EnemyBehaviour tempEnemyBehaviour;
for (int i = 0; i < activatedEnemyListIndex.Count; i++)
{
tempEnemyBehaviour = LevelManager.globalInstance.GetEnemyBehaviourFromIndex(activatedEnemyListIndex[i]);
if (tempEnemyBehaviour.IsCoroutineRunning() == false && tempEnemyBehaviour.gameObject.activeSelf)//gotta check if activeSelf in edge-case monster dies before this RPC arrives
tempEnemyBehaviour.StartCoroutine(tempEnemyBehaviour.UpdateBehaviour());
}
}
public void ResetEnemiesVelocity()
{
for (int i = 0; i < activatedEnemyListIndex.Count; i++)
{
//If active enemy behaviour
if (LevelManager.globalInstance.GetEnemyBehaviourFromIndex(activatedEnemyListIndex[i]).enabled)
LevelManager.globalInstance.GetEnemyRigidbodyFromIndex(activatedEnemyListIndex[i]).velocity = Vector2.zero;
}
}
}