Prediction/Synchronization
Scott Marchant

Inhoud
- Inleiding
- Verschillende prediction technieken
2.1 Deterministic Lockstep
2.2 Snapshot Interpolation
2.3 State Synchronization
2.4 Welke heb ik gekozen en waarom - Proces
3.1 Research
3.2 Opzet en Unet
3.3 Movement en prediction
3.4 Server Reconciliation
3.5 Dead reckoning - Eindresultaat
- Toekomst
- Bronnen
1. Inleiding
Hoe maak ik prediction in een multiplayer game? Ik ben al vrij lang geïnteresseerd in het maken van een multiplayer game. Om hier dan iets meer over te leren vond ik een grootte kans. Hoe je bijvoorbeeld er voor zorgt dat je lag compenseert en je eigen synchronisatie code maakt.
Ik heb hier gebruik gemaakt van Unet. De multiplayer oplossing van Unity, omdat ik hier al een klein beetje ervaring mee heb. De bedoeling is dat synchronizatie problemen opgelost wordt en latency verminderd word.


2. Verschillende prediction technieken
Er zijn drie verschillende vormen van prediction. Hier ga ik uitleggen welke drie er zijn, wat ze inhouden en voor welke ik heb gekozen en waarom. Je hebt Deterministic lockstep, Snapshot Interpolation of State Synchronization.
2.1 Deterministic lockstep
Hierbij word er alleen gekeken naar de player-input. Dit word veel gebruikt bij strategie games. Omdat het deterministic is zorgt het ervoor dat er niks willekeurigs kan gebeuren. Dit zorgt er dan ook weer voor dat het niet gebruikt kan worden voor race of first person shooter games. Deze vorm neemt weinig bandbreedte in. Dit is het beste voor twee tot vier spelers. (Fiedler, 2014)
2.2 Snapshot Interpolation
Hier word gebruikt gemaakt van de positie en de rotatie van de speler. Die word als een snapshot meegegeven aan de server. De server stuurt dit dan weer naar de rest van de clients. Dit is een goede techniek om te gebruiken maar neemt veel bandbreedte in. Met deze techniek kan je meer spelers kwijt dan bij deterministic lockstep. (Fiedler, 2014)
2.3 State Synchronization
Dit is een techniek waarbij je eigenlijk de twee vorige met elkaar combineert. Hierbij gebruik je de input en de state van de speler om prediction uit te voeren. (Fiedler, 2015)
2.4 Welke vorm gekozen?
Ik heb gekozen voor state synchronization. Deterministic lockstep viel direct al weg, omdat ik prediction wou gebruiken in een fast-paced game. Snapshot interpolation is na deterministic lockstep de beste keuze alleen neemt dit veel bandbreedte in beslag. Bij state synchronization doe je een deel van beide en kan je kiezen hoeveel je update, hierdoor word er minder bandbreedte gebruikt.
3. Proces
3.1 Research
Toen ik hier aan begon ging ik kijken hoe je het kan toepassen op unet. Later kwam ik er achter dat Unet zijn eigen versie van synchronisatie had genaamd NetworkTransform. Door hier weer een beetje onderzoek naar te doen kwam ik er achter dat hun eigen synchronisatie veel bandbreedte in neemt. Door middel van je eigen code te schrijven kan je dat dus tegen gaan. Later kwam ik uit bij een GDC talk over prediction van Glenn Fiedler die drie technieken uitlegde. Zo kwam ik bij state synchronization uit.
3.2 Opzet en Unet
Om te beginnen moest ik Unet installeren in het project om een multiplayer connectie te kunnen starten. Hiervoor hebben ze verschillende componenten waarbij je makkelijk een netwerk op kan zetten en de HUD ervoor.

Figuur 4: De NetworkManager met components
Daarna komt de plane voor het terrein en de blokjes als de spelers.
3.3 Movement en Prediction
Movement voor een game is vrij simpel op te zetten. Als je op W klikt gaat je blokje naar voren, met S gaat hij naar achter. A en D zorgen voor de rotatie. Alleen in dit geval moet het over het netwerk gaan. Omdat ik de state synchronization techniek wil gebruiken moeten we de input meenemen in de state die we versturen.
void FixedUpdate() { if (isLocalPlayer) { PlayerInput playerInput = GetPlayerInput(); if (playerInput != null) { pendingMoves.Add(playerInput); UpdatePredictedState(); CmdMoveOnServer(playerInput); } } SyncState(); }
Ik zet de input van de local speler in een lijst, daarna word de state van de local speler geupdate en word de input door gestuurd naar server state. De server state stuurt dat dan weer door naar de clients.
void SyncState() { if (isServer) { transform.position = state.position; transform.rotation = state.rotation; return; } PlayerState stateToRender = isLocalPlayer ? predictedState : state;
De synchronisatie is er nu, maar het kan nog steeds gebeuren dat vanwege lag de synchronisatie niet goed gaat. Daarvoor hebben we server reconciliation.
3.4 Server Reconciliation
[SyncVar(hook = "OnServerStateChanged")] public PlayerState state;
public void OnServerStateChanged(PlayerState newState) { state = newState; if (pendingMoves != null) { while (pendingMoves.Count > (predictedState.timestamp - state.timestamp)) { pendingMoves.RemoveAt(0); } UpdatePredictedState(); } }
Als de Playerstate veranderd word de OnServerStateChanged functie aangeroepen. Dit zorgt er voor dat hij kijkt naar de tijd van de predicted state en alle input die daarvoor kwam weg gooit uit de lijst. zodat alle andere clients kunnen synchroniseren met de rest. Daarna om alles smooth te laten verlopen heb ik een lerpeasing en lerpspacing.
transform.position = Vector3.Lerp(transform.position, stateToRender.position * Settings.PlayerLerpSpacing, Settings.PlayerLerpEasing); transform.rotation = Quaternion.Lerp(transform.rotation, stateToRender.rotation, Settings.PlayerLerpEasing);
3.5 Dead reckoning
Dead reckoning is een methode om soepele bewegingen van objecten en spelers te behouden, zelfs bij netwerkvertragingen. Het voorspelt de toekomstige positie van objecten op basis van hun huidige beweging, waardoor bewegingen er vloeiender uitzien, zelfs als er vertragingen zijn bij het bijwerken van posities vanuit het netwerk.
Er zijn twee nieuwe variabelen bij gemaakt: extrapolationDelay en lastNetworkTime.


Ook word er in de PlayerState nu de velocity van de speler bij gehouden.
ExtrapolationDelay word gebruikt zodat de extrapolatie niet direct word toegepast, maar pas na een bepaald aantal tijd. LastNetworktime word gebruikt om bij te houden wanneer de laatste keer netwerkcommunicatie heeft plaatsgevonden. Als de huidige lokale tijd groter is dan lastNetworkTime plus extrapolationDelay, dan begint de extrapolatie van de positie en rotatie van de speler.

Vector3 extrapolatedPosition = state.position + state.velocity * extrapolationDuration;
Hier wordt de positie van de speler geëxtrapoleerd op basis van de laatst ontvangen positie (state.position) en de snelheid (state.velocity). De extrapolationDuration geeft aan hoelang de extrapolatie moet duren. Het resultaat is een nieuwe positie waarvan wordt aangenomen dat de speler zich daar bevindt na de verstreken tijd.

Dit is een functie op de server die de bewegingsinvoer van de speler verwerkt. Het gebruikt de ProcessPlayerInput-functie om de nieuwe spelerstoestand te berekenen en bij te werken. lastNetworkTime wordt bijgewerkt om de tijd van de laatste netwerkcommunicatie vast te leggen.

Deze functie wordt op de client aangeroepen wanneer de serverstaat verandert. Deze functie werkt de lokale state bij met de nieuwe serverstaat. Indien er nog invoeren in de wachtrij zijn, worden overbodige inputs verwijderd, zodat alleen relevante inputs blijven. Ook wordt UpdatePredictedState aangeroepen om de voorspelde staat bij te werken.

Deze functie werkt de voorspelde spelerstoestand bij op basis van opgeslagen input. Het begint met de huidige staat en past vervolgens elke invoer toe uit de wachtrij om de nieuwe voorspelde staat te berekenen.
4. Eindresultaat
Het eind resultaat is dus een fast-paced movement synchronisatie. Die door middel van prediction en server reconciliation lag reduceert. De code die ik heb geschreven kan je ook herbruiken voor andere games. Zo heb ik dat getest op het project van de lava shader van een mede-student.

5. Toekomst
Als ik hiermee door zou gaan zou ik gameplay-mechanismen die gebruikmaken van de voorspellingen, zoals vaardigheden of wapens die invloed hebben op de voorspelde bewegingen erin willen zetten. Unet is deprecated en Unity heeft een nieuwe multiplayer framework gemaakt “Netcode for Gameobjects”. Hier zou ik ook in willen leren hoe je prediction/dead-reckoning in implementeerd.
6. Bronnen
Arellano, C. (2015, 22 september). UNET Unity 5 Networking Tutorial Part 1 of 3 – Introducing the HLAPI. Geraadpleegd op 11 maart 2021, van https://www.gamasutra.com/blogs/ChristianArellano/20150922/254218/UNET_Unity_5_Networking_Tutorial_Part_1_of_3__Introducing_the_HLAPI.php
Fiedler, G. (2014, 29 november). Deterministic Lockstep. Geraadpleegd op 11 maart 2021, van https://gafferongames.com/post/deterministic_lockstep/
Fiedler, G. (2014, 30 november). Snapshot Interpolation. Geraadpleegd op 11 maart 2021, van https://gafferongames.com/post/snapshot_interpolation/
Fiedler, G. (2015, 5 januari). State Synchronization. Geraadpleegd op 11 maart 2021, van https://gafferongames.com/post/state_synchronization/
Gambetta, G. (2020). Client-Side Prediction and Server Reconciliation – Gabriel Gambetta. Geraadpleegd op 15 maart 2021, van https://www.gabrielgambetta.com/client-side-prediction-server-reconciliation.html
Physics for Game Programmers : Networking for Physics Programmers. (2015, 2 maart). [Videobestand]. Geraadpleegd van https://www.gdcvault.com/play/1022195/Physics-for-Game-Programmers-Networking
Source Multiplayer Networking. (2019). [Afbeelding]. Geraadpleegd van https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking